Skip to content

Conversation

@kmcfaul
Copy link
Contributor

@kmcfaul kmcfaul commented Dec 12, 2025

What: Closes #11146, #12149

Compass Demo: https://pf-react-pr-12175.surge.sh/ai/generative-uis/compass/react-demos/docked-nav-demo/
Page Demo: https://pf-react-pr-12175.surge.sh/components/page/react-demos/docked-nav/

Summary by CodeRabbit

  • New Features

    • Docked navigation support added to Compass, Masthead, Nav, and Page (new dock/variant options)
    • Toolbar gains a vertical layout option
    • Nav items now support ref-forwarding
  • Documentation

    • New demos and examples showcasing docked navigation and layouts (including Compass and Page docked demos)
    • Added demo styling for docked layouts
  • Tests

    • Added unit tests covering docked variants and toolbar vertical behavior
  • Chores

    • Bumped PatternFly packages from 6.5.0-prerelease.27 to 6.5.0-prerelease.33

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Walkthrough

Adds docked-navigation support across multiple UI components (Compass, Nav, Masthead, Page, Toolbar), refactors NavItem to forward refs, updates demos/examples and CSS for docked layouts, adds tests, and bumps @patternfly/patternfly to 6.5.0-prerelease.33.

Changes

Cohort / File(s) Summary
Dependency updates
\packages/react-core/package.json`, `packages/react-docs/package.json`, `packages/react-icons/package.json`, `packages/react-styles/package.json`, `packages/react-tokens/package.json``
Bumped @patternfly/patternfly from 6.5.0-prerelease.27 to 6.5.0-prerelease.33.
Compass component & tests
\packages/react-core/src/components/Compass/Compass.tsx`, `packages/react-core/src/components/Compass/tests/Compass.test.tsx``
Added dock?: React.ReactNode, conditional layout rendering, applies dock modifier class when dock present; tests for dock modifier presence/absence.
Compass examples & styles
\packages/react-core/src/components/Compass/examples/Compass.md`, `packages/react-core/src/components/Compass/examples/CompassDockLayout.tsx`, `packages/react-core/src/components/Compass/examples/compass.css`, `packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx``
New docked-nav docs/examples and CSS for docked visualization; added CompassBasic/CompassDockDemo examples.
Masthead variant & tests
\packages/react-core/src/components/Masthead/Masthead.tsx`, `packages/react-core/src/components/Masthead/tests/Masthead.test.tsx``
Added `variant?: 'default'
Nav variant & tests
\packages/react-core/src/components/Nav/Nav.tsx`, `packages/react-core/src/components/Nav/tests/Nav.test.tsx``
Extended NavProps.variant to include 'docked' and apply docked modifier class; test added.
NavItem ref forwarding
\packages/react-core/src/components/Nav/NavItem.tsx``
Refactored into NavItemBase + forwardRef wrapper; added anchorRef and innerRef props; updated ref handling and typings.
Page variant & tests
\packages/react-core/src/components/Page/Page.tsx`, `packages/react-core/src/components/Page/tests/Page.test.tsx``
Added `variant?: 'default'
Toolbar vertical prop & tests
\packages/react-core/src/components/Toolbar/Toolbar.tsx`, `packages/react-core/src/components/Toolbar/tests/Toolbar.test.tsx``
Added isVertical?: boolean (beta) prop, applies vertical modifier class; tests updated/added.
Page docs & demo
\packages/react-core/src/demos/Page.md`, `packages/react-core/src/demos/examples/Page/PageDockedNav.tsx``
Added "Docked nav" docs and new PageDockedNav demo component showcasing a docked layout with vertical toolbar, nav, avatar dropdown, and gallery.
Integration test update
\packages/react-integration/cypress/integration/drawer.spec.ts``
Adjusted expected CSS flex-basis value in panel width assertion.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • packages/react-core/src/components/Nav/NavItem.tsx: forwardRef typing, innerRef vs anchorRef behavior, and consumer ref access.
    • Consistency of modifier keys (docked/dock) across Masthead, Nav, Page, Compass, Toolbar vs style module keys.
    • Compass.tsx conditional rendering: ensure inert attributes and drawer logic behave when regions are omitted.
    • New demos/examples and CSS: compile-time imports and tooltip/dropdown ref targets.
    • Tests: verify they use style module class names (not hardcoded strings).

Poem

🐰 I hop beside the dock so neat,
Icons snug in rows, a tiny seat,
Masthead, Nav, and Page align,
Refs forwarded on a tidy line,
Docked and vertical — carrots for the fleet! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (5 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 being added: support for docked navigation layout across multiple components.
Linked Issues check ✅ Passed The PR successfully implements docked nav variant support for Nav, Page, Compass, and Masthead components with variant props and styling, aligning with issue #11146 requirements.
Out of Scope Changes check ✅ Passed All changes are within scope: component variant implementations, tests, demos, and dependency updates all support the docked nav feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@patternfly-build
Copy link
Collaborator

patternfly-build commented Dec 12, 2025

Copy link

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

♻️ Duplicate comments (4)
packages/react-docs/package.json (1)

26-26: Version update verified.

This dependency update aligns with the coordinated PatternFly prerelease version bump across all packages. The verification comment in packages/react-styles/package.json covers this update as well.

packages/react-icons/package.json (1)

36-36: Version update verified.

This devDependency update is consistent with the coordinated PatternFly prerelease version bump across all packages.

packages/react-tokens/package.json (1)

33-33: Version update verified.

This devDependency update is consistent with the coordinated PatternFly prerelease version bump across all packages.

packages/react-core/package.json (1)

57-57: Version update verified.

This devDependency update is consistent with the coordinated PatternFly prerelease version bump across all packages.

🧹 Nitpick comments (8)
packages/react-core/src/demos/Page.md (1)

23-27: Consider retaining explanatory context.

The removed paragraph provided helpful context about the isManagedSidebar prop behavior. While the numbered list below still provides manual control instructions, the introductory explanation clarified why these examples use isManagedSidebar. Consider whether this context loss impacts user understanding.

packages/react-core/src/components/Compass/__tests__/Compass.test.tsx (1)

174-182: Nice focused dock coverage; consider adding a dock={null} edge-case test if supported. This will enforce the intended semantics of “provided” vs “not provided”.

packages/react-core/src/components/Compass/examples/CompassDockLayout.tsx (1)

10-25: Rename export to match the example intent (DockLayout vs “Basic”) and docs references.
Right now the file is CompassDockLayout.tsx but it exports CompassBasic, which is easy to mis-wire in docs and imports.

-export const CompassBasic: React.FunctionComponent = () => {
+export const CompassDockLayout: React.FunctionComponent = () => {
   const dockContent = <div>Content</div>;
   const mainContent = (
     <CompassContent>
       <CompassMainHeader>
         <CompassPanel>
           <CompassMainHeaderContent>
             <div>Content title</div>
           </CompassMainHeaderContent>
         </CompassPanel>
       </CompassMainHeader>
       <div>Content</div>
     </CompassContent>
   );
   return <Compass dock={dockContent} main={mainContent} />;
 };
packages/react-core/src/components/Page/Page.tsx (1)

335-362: Avoid rendering an empty “dock” wrapper when masthead is not provided.
Right now {variant === 'docked' ? <div ...>{masthead}</div> : masthead} will still render an empty dock container when masthead is undefined/null.

-          {variant === 'docked' ? <div className={css(styles.pageDock)}>{masthead}</div> : masthead}
+          {variant === 'docked' && masthead ? <div className={css(styles.pageDock)}>{masthead}</div> : masthead}
packages/react-core/src/components/Nav/Nav.tsx (1)

38-40: Update the variant doc comment (no longer “for horizontal navs” only).
Now that variant includes docked (and already included default/subnav), the comment should describe it as the general layout variant.

packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (2)

55-97: Remove or complete the unused isOpen + global window listeners.
As written, isOpen is never set true and the refs aren’t attached, so the event listeners don’t provide value and add noise to the demo.


222-235: Type handleClick and avoid implicit any.

-  const handleClick = (event) => {
+  const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
     event.preventDefault();
     const mainContentElement = document.getElementById(mainContainerId);
     if (mainContentElement) {
       mainContentElement.focus();
     }
   };
packages/react-core/src/components/Masthead/__tests__/Masthead.test.tsx (1)

1-3: Good targeted coverage for the new variant behavior.
These class-based assertions are a nice complement to snapshots. Note: this test file uses toHaveClass without explicitly importing @testing-library/jest-dom (unlike other test files in the codebase)—consider adding the import for consistency: import '@testing-library/jest-dom';

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5053dd and 86f7b1b.

⛔ Files ignored due to path filters (2)
  • packages/react-core/src/demos/assets/PF-IconLogo-color.svg is excluded by !**/*.svg
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (21)
  • packages/react-core/package.json (1 hunks)
  • packages/react-core/src/components/Compass/Compass.tsx (3 hunks)
  • packages/react-core/src/components/Compass/__tests__/Compass.test.tsx (1 hunks)
  • packages/react-core/src/components/Compass/examples/Compass.md (2 hunks)
  • packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1 hunks)
  • packages/react-core/src/components/Compass/examples/CompassDockLayout.tsx (1 hunks)
  • packages/react-core/src/components/Compass/examples/compass.css (1 hunks)
  • packages/react-core/src/components/Masthead/Masthead.tsx (2 hunks)
  • packages/react-core/src/components/Masthead/__tests__/Masthead.test.tsx (2 hunks)
  • packages/react-core/src/components/Nav/Nav.tsx (2 hunks)
  • packages/react-core/src/components/Nav/NavItem.tsx (4 hunks)
  • packages/react-core/src/components/Nav/__tests__/Nav.test.tsx (1 hunks)
  • packages/react-core/src/components/Page/Page.tsx (4 hunks)
  • packages/react-core/src/components/Page/__tests__/Page.test.tsx (1 hunks)
  • packages/react-core/src/components/Toolbar/Toolbar.tsx (3 hunks)
  • packages/react-core/src/demos/Page.md (3 hunks)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1 hunks)
  • packages/react-docs/package.json (1 hunks)
  • packages/react-icons/package.json (1 hunks)
  • packages/react-styles/package.json (1 hunks)
  • packages/react-tokens/package.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
packages/react-core/src/components/Masthead/__tests__/Masthead.test.tsx (1)
packages/react-core/src/components/Masthead/Masthead.tsx (1)
  • Masthead (34-59)
packages/react-core/src/components/Compass/__tests__/Compass.test.tsx (1)
packages/react-core/src/components/Compass/Compass.tsx (1)
  • Compass (41-123)
packages/react-core/src/components/Page/__tests__/Page.test.tsx (1)
packages/react-core/src/components/Page/Page.tsx (2)
  • render (227-364)
  • Page (367-367)
packages/react-core/src/components/Compass/examples/CompassDockLayout.tsx (5)
packages/react-core/src/components/Compass/CompassContent.tsx (1)
  • CompassContent (16-40)
packages/react-core/src/components/Compass/CompassMainHeader.tsx (1)
  • CompassMainHeader (27-52)
packages/react-core/src/components/Compass/CompassPanel.tsx (1)
  • CompassPanel (23-49)
packages/react-core/src/components/Compass/CompassMainHeaderContent.tsx (1)
  • CompassMainHeaderContent (15-23)
packages/react-core/src/components/Compass/Compass.tsx (1)
  • Compass (41-123)
packages/react-core/src/components/Nav/NavItem.tsx (3)
packages/react-core/src/demos/examples/JumpLinks/JumpLinksWithDrawer.js (1)
  • useContext (24-24)
packages/react-core/src/components/Nav/Nav.tsx (1)
  • NavContext (63-63)
packages/react-core/src/components/Page/PageSidebar.tsx (1)
  • PageSidebarContext (28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build, test & deploy
🔇 Additional comments (19)
packages/react-core/src/demos/Page.md (3)

6-6: LGTM!

The additional React hooks (useRef, useEffect) are appropriate for the new docked navigation demo functionality.


16-21: LGTM!

The new icon imports (CubeIcon, FolderIcon, CloudIcon, CodeIcon) and pfIconLogo asset support the docked navigation demo implementation.


58-62: LGTM!

The new "Docked nav" section aligns with the PR objectives to add support for docked navigation layout. The placement after the context selector section is logical.

packages/react-core/src/components/Toolbar/Toolbar.tsx (2)

43-44: LGTM!

The new isVertical prop is appropriately marked as @beta and follows the existing pattern for orientation/layout modifier props in the Toolbar component.


148-148: LGTM!

The implementation correctly destructures the isVertical prop and conditionally applies the styles.modifiers.vertical class when truthy, following the established pattern for other boolean modifiers in this component.

Also applies to: 173-173

packages/react-core/src/components/Compass/examples/compass.css (3)

17-23: LGTM!

The expansion of the alternate-footer rule with full positioning and border properties provides consistent styling across the compass variants.


25-35: LGTM!

The new ws-react-a-compass-with-docked-nav styling block appropriately supports the docked navigation layout feature by applying positioning and decorative borders, consistent with the existing compass variant patterns.


1-1: The selector prefix change from ws-react-p- to ws-react-a- is intentional and complete.

No remaining references to the old prefix exist in the codebase. This CSS file contains demo-specific selectors used by the documentation system for Compass examples. The migration is already fully applied across all three example containers.

packages/react-styles/package.json (1)

22-22: Verify the prerelease version before merging.

The @patternfly/patternfly dependency is being updated to a prerelease version 6.5.0-prerelease.32. While this version exists on npm, prerelease versions are less stable than stable releases. Ensure this version does not introduce breaking changes or known issues that could affect the docked navigation feature before proceeding with the update.

packages/react-core/src/components/Compass/Compass.tsx (3)

8-39: Public API: dock?: React.ReactNode looks fine, but clarify “provided” semantics. The prop addition is straightforward; just ensure docs/examples align on whether null means “not provided”.


76-108: Verify CSS/layout expectations after making regions conditional. If compass.css uses named grid-areas that assume the header/sidebar/footer nodes always exist, removing them could change layout (even when content is absent).


69-109: Fix dock modifier/render condition mismatch (dock !== undefined vs {dock && …}). Today dock={null} (or 0/'') applies the dock modifier but may not render the dock region. Prefer a single dock != null check for both.

-    <div
-      className={css(styles.compass, dock !== undefined && styles.modifiers.dock, className)}
+    <div
+      className={css(styles.compass, dock != null && styles.modifiers.dock, className)}
       {...props}
       style={{ ...props.style, ...backgroundImageStyles }}
     >
-      {dock && <div className={css(`${styles.compass}__dock`)}>{dock}</div>}
+      {dock != null && <div className={css(`${styles.compass}__dock`)}>{dock}</div>}
       {header && (
packages/react-core/src/components/Nav/NavItem.tsx (2)

98-101: Double-check setFlyoutRef(ref) expectation after ref changes. If NavContext compares ref object identity (not DOM node identity), switching to a stable internal ref is important; avoid storing a callback ref in context.


285-287: The current prop order is correct and doesn't need to be changed.

NavItemBase explicitly destructures innerRef from props (line 76), which removes it from the remaining props object before spreading. Therefore, {...props} cannot override innerRef={ref} regardless of the order. The prop precedence concern doesn't apply to this implementation.

Likely an incorrect or invalid review comment.

packages/react-core/src/components/Nav/__tests__/Nav.test.tsx (1)

263-276: Good coverage for docked variant class. The test cleanly asserts the modifier application without overcoupling to markup.

packages/react-core/src/components/Page/__tests__/Page.test.tsx (1)

393-408: Dock variant tests look solid and match implementation (styles.modifiers.dock).

packages/react-core/src/components/Page/Page.tsx (1)

18-25: Variant prop addition looks good.
The union type + @beta doc is consistent with the rest of the PR’s “docked” rollout.

packages/react-core/src/components/Masthead/Masthead.tsx (1)

7-32: CSS modifier verification confirmed. The variant === 'docked' && styles.modifiers.docked pattern is correct and the modifier is available—confirmed by test coverage that explicitly validates the docked class is applied when variant is set to 'docked' and omitted otherwise.

export const Masthead: React.FunctionComponent<MastheadProps> = ({
  children,
  className,
  display = {
    md: 'inline'
  },
  inset,
  variant = 'default',
  ...props
}: MastheadProps) => {
  const { width, getBreakpoint } = useContext(PageContext);
  return (
    <header
      className={css(
        styles.masthead,
        variant === 'docked' && styles.modifiers.docked,
        formatBreakpointMods(display, styles, 'display-', getBreakpoint(width)),
        formatBreakpointMods(inset, styles, '', getBreakpoint(width)),
        className
      )}
      {...props}
    >
      {children}
    </header>
  );
};
packages/react-core/src/components/Nav/Nav.tsx (1)

126-169: Docked modifier is properly wired and tested. The styles.modifiers.docked reference in the className is correct and has explicit test coverage in Nav.test.tsx confirming the class is applied when variant="docked". The modifier is imported from the PatternFly react-styles package and is used consistently across Nav, Masthead, and Page components.

Comment on lines +53 to +52
const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
typeof selectedItem.itemId === 'number' && setActiveItem(selectedItem.itemId);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Nav selection is incorrect: duplicated itemId values + mismatched isActive.
Items 3/4 currently can’t become active because they use itemId={0} while checking activeItem === 2/3.

-  const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
-    typeof selectedItem.itemId === 'number' && setActiveItem(selectedItem.itemId);
-  };
+  const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
+    if (typeof selectedItem.itemId === 'number') {
+      setActiveItem(selectedItem.itemId);
+    }
+  };

...
                   <NavItem
                     key="nav-icon-link3"
                     preventDefault
                     id="nav-icon-link3"
                     to="#nav-icon-link3"
-                    itemId={0}
+                    itemId={2}
                     isActive={activeItem === 2}
                     icon={<CloudIcon />}
                     ref={navItem3Ref}
+                    aria-label="Link 3"
                   />
...
                   <NavItem
                     key="nav-icon-link4"
                     preventDefault
                     id="nav-icon-link4"
                     to="#nav-icon-link4"
-                    itemId={0}
+                    itemId={3}
                     isActive={activeItem === 3}
                     icon={<CodeIcon />}
                     ref={navItem4Ref}
+                    aria-label="Link 4"
                   />

(Also consider adding aria-label for Link 1/2 as well, since these are icon-only.)

Also applies to: 121-167

🤖 Prompt for AI Agents
packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx lines
53-55: the Nav items have duplicated/mismatched itemId values causing items 3
and 4 to never become active (they use itemId={0} while active checks compare to
2/3); update the itemId props so each Nav item has a unique numeric id that
matches the activeItem comparisons (e.g., set the third and fourth items to
itemId={2} and itemId={3} or adjust activeItem logic to match intended ids),
ensure the onNavSelect continues to setActiveItem(selectedItem.itemId) for
number ids, and apply the same corrections to the duplicated block at lines
121-167; additionally add appropriate aria-label attributes to the icon-only
Link components for Link 1 and Link 2.

Copy link

@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: 0

♻️ Duplicate comments (6)
packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (2)

46-92: Dead/ineffective “menu open” logic + hook deps warning — remove it or wire it fully.
isOpen is never set true, menuRef / outer toggleRef aren’t attached to DOM nodes, and the effect registers global listeners regardless; lint correctly warns about missing deps (because handlers are recreated every render). If this demo doesn’t actually have a dock flyout/drawer yet, simplest is to delete isOpen, menuRef, toggleRef, both handlers, and the useEffect.

-import { useRef, useState, useEffect } from 'react';
+import { useRef, useState } from 'react';
@@
   const [activeItem, setActiveItem] = useState<number>(0);
   const [isDropdownOpen, setIsDropdownOpen] = useState(false);
-  const [isOpen, setIsOpen] = useState<boolean>(false);
-  const menuRef = useRef<HTMLDivElement>(null);
-  const toggleRef = useRef<HTMLButtonElement>(null);
@@
-  const handleMenuKeys = (event: KeyboardEvent) => {
-    if (!isOpen) {
-      return;
-    }
-    if (menuRef.current?.contains(event.target as Node) || toggleRef.current?.contains(event.target as Node)) {
-      if (event.key === 'Escape') {
-        setIsOpen(!isOpen);
-        toggleRef.current?.focus();
-      }
-    }
-  };
-
-  const handleClickOutside = (event: MouseEvent) => {
-    if (isOpen && !menuRef.current?.contains(event.target as Node)) {
-      setIsOpen(false);
-    }
-  };
-
-  useEffect(() => {
-    window.addEventListener('keydown', handleMenuKeys);
-    window.addEventListener('click', handleClickOutside);
-
-    return () => {
-      window.removeEventListener('keydown', handleMenuKeys);
-      window.removeEventListener('click', handleClickOutside);
-    };
-  }, [isOpen, menuRef]);

53-55: Nav selection is broken (duplicate itemId + mismatched isActive) and icon-only items need names.
Items 3/4 can’t become active because they set itemId={0} but compare activeItem === 2/3. Also these are icon-only; add aria-label (or children text) so the links are accessible.

@@
   const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
-    typeof selectedItem.itemId === 'number' && setActiveItem(selectedItem.itemId);
+    if (typeof selectedItem.itemId === 'number') {
+      setActiveItem(selectedItem.itemId);
+    }
   };
@@
                   <NavItem
                     key="nav-icon-link1"
@@
                     itemId={0}
                     isActive={activeItem === 0}
                     icon={<CubeIcon />}
                     ref={navItem1Ref}
+                    aria-label="Link 1"
                   />
@@
                   <NavItem
                     key="nav-icon-link2"
@@
                     itemId={1}
                     isActive={activeItem === 1}
                     icon={<FolderIcon />}
                     ref={navItem2Ref}
+                    aria-label="Link 2"
                   />
@@
                   <NavItem
                     key="nav-icon-link3"
@@
-                    itemId={0}
+                    itemId={2}
                     isActive={activeItem === 2}
                     icon={<CloudIcon />}
                     ref={navItem3Ref}
+                    aria-label="Link 3"
                   />
@@
                   <NavItem
                     key="nav-icon-link4"
@@
-                    itemId={0}
+                    itemId={3}
                     isActive={activeItem === 3}
                     icon={<CodeIcon />}
                     ref={navItem4Ref}
+                    aria-label="Link 4"
                   />

Also applies to: 141-160

packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (3)

55-98: Remove or wire the unused isOpen menu state + global listeners (currently dead code).
Same issue as the Compass dock demo: isOpen is never set true, refs aren’t attached, and the effect will re-register handlers as they change. If there’s no flyout/drawer yet, delete this block.

-import { useEffect, useRef, useState } from 'react';
+import { useRef, useState } from 'react';
@@
-  const [isOpen, setIsOpen] = useState<boolean>(false);
-  const menuRef = useRef<HTMLDivElement>(null);
-  const toggleRef = useRef<HTMLButtonElement>(null);
@@
-  const handleMenuKeys = (event: KeyboardEvent) => {
-    if (!isOpen) {
-      return;
-    }
-    if (menuRef.current?.contains(event.target as Node) || toggleRef.current?.contains(event.target as Node)) {
-      if (event.key === 'Escape') {
-        setIsOpen(!isOpen);
-        toggleRef.current?.focus();
-      }
-    }
-  };
-
-  const handleClickOutside = (event: MouseEvent) => {
-    if (isOpen && !menuRef.current?.contains(event.target as Node)) {
-      setIsOpen(false);
-    }
-  };
-
-  useEffect(() => {
-    window.addEventListener('keydown', handleMenuKeys);
-    window.addEventListener('click', handleClickOutside);
-
-    return () => {
-      window.removeEventListener('keydown', handleMenuKeys);
-      window.removeEventListener('click', handleClickOutside);
-    };
-  }, [isOpen, menuRef]);

42-43: Avoid @patternfly/react-core/src/... asset self-imports in demos.
These internal paths are brittle for consumers and can break under different bundlers/resolution. Prefer relative imports within the repo (or copy assets into the demo folder).

-import imgAvatar from '@patternfly/react-core/src/components/assets/avatarImg.svg';
-import pfIconLogo from '@patternfly/react-core/src/demos/assets/PF-IconLogo-color.svg';
+import imgAvatar from '../../../components/assets/avatarImg.svg';
+import pfIconLogo from '../../assets/PF-IconLogo-color.svg';

157-175: Fix broken Nav selection + duplicate DOM ids (id/to) for items 3/4.
Items 3/4 reuse id="nav-icon-link1"/to="#nav-icon-link1" and itemId={0}, so you get duplicate DOM ids and the active state can’t work.

                   <NavItem
                     preventDefault
-                    id="nav-icon-link1"
-                    to="#nav-icon-link1"
-                    itemId={0}
+                    id="nav-icon-link3"
+                    to="#nav-icon-link3"
+                    itemId={2}
                     isActive={activeItem === 2}
                     icon={<CloudIcon />}
                     ref={navItem3Ref}
+                    aria-label="Link 3"
                   />
                   <NavItem
                     preventDefault
-                    id="nav-icon-link1"
-                    to="#nav-icon-link1"
-                    itemId={0}
+                    id="nav-icon-link4"
+                    to="#nav-icon-link4"
+                    itemId={3}
                     isActive={activeItem === 3}
                     icon={<CodeIcon />}
                     ref={navItem4Ref}
+                    aria-label="Link 4"
                   />
packages/react-core/src/components/Nav/NavItem.tsx (1)

55-57: Fix ref-forwarding implementation: current code can crash with callback refs and lets props override the forwarded ref.
Right now innerRef is React.Ref<any> (callback refs are valid), but it’s cast to MutableRefObject and then used as ref.current, which will throw if a callback ref is passed. Also <NavItemBase innerRef={ref} {...props} /> allows {...props} to override innerRef.

@@
 export interface NavItemProps extends Omit<React.HTMLProps<HTMLAnchorElement>, 'onClick'>, OUIAProps {
@@
   /** @hide Forwarded ref */
-  innerRef?: React.Ref<any>;
+  innerRef?: React.Ref<HTMLLIElement>;
 }
@@
-  const _ref = useRef<HTMLLIElement>(undefined);
-  const ref = (innerRef as MutableRefObject<HTMLLIElement>) || _ref;
-  const flyoutVisible = ref === flyoutRef;
+  const _ref = useRef<HTMLLIElement | null>(null);
+  const setRefs = (node: HTMLLIElement | null) => {
+    // keep internal ref updated
+    _ref.current = node;
+    // forward to innerRef (callback or object)
+    if (typeof innerRef === 'function') innerRef(node);
+    else if (innerRef && 'current' in innerRef) (innerRef as React.MutableRefObject<HTMLLIElement | null>).current = node;
+  };
+  const ref = innerRef ?? _ref;
+  const flyoutVisible = (flyoutRef as React.RefObject<HTMLLIElement | null>)?.current === _ref.current;
@@
       <li
@@
-        ref={ref}
+        ref={setRefs}
         {...ouiaProps}
       >
@@
-export const NavItem = forwardRef((props: NavItemProps, ref: React.Ref<any>) => (
-  <NavItemBase innerRef={ref} {...props} />
+export const NavItem = forwardRef<HTMLLIElement, NavItemProps>((props, ref) => (
+  <NavItemBase {...props} innerRef={ref} />
 ));

If NavContextProps.flyoutRef is not a RefObject<HTMLLIElement | null>, you’ll need to align its type too so flyoutVisible remains correct (compare nodes, not ref object identity).

Also applies to: 83-86, 245-260, 264-269, 285-287

🧹 Nitpick comments (3)
packages/react-core/src/components/Page/__tests__/Page.test.tsx (1)

393-408: LGTM! Comprehensive coverage for the docked variant.

The three test cases correctly verify all scenarios for the new variant prop: when it's set to "docked" (applies the modifier), when it's "default" (doesn't apply), and when it's omitted (doesn't apply). The tests follow the same pattern as existing modifier tests in this file.

Optional: Consider consolidating these three similar tests using test.each for slightly better maintainability:

+  test.each([
+    ['docked', true],
+    ['default', false],
+    [undefined, false]
+  ] as const)(`Renders with ${styles.modifiers.dock} class when variant is %s: %s`, (variant, shouldHaveClass) => {
+    render(<Page {...props} variant={variant} data-testid="page"></Page>);
+    
+    if (shouldHaveClass) {
+      expect(screen.getByTestId('page')).toHaveClass(styles.modifiers.dock);
+    } else {
+      expect(screen.getByTestId('page')).not.toHaveClass(styles.modifiers.dock);
+    }
+  });

However, the current approach is perfectly fine and consistent with the rest of the test suite.

packages/react-core/src/components/Compass/__tests__/Compass.test.tsx (1)

173-182: Consider also asserting dock content renders (not just the modifier class).
Right now these tests can pass even if the dock slot fails to render but the modifier class is applied. Adding a getByText('Dock content') in the “dock is passed” case would tighten coverage.

packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1)

222-229: Type handleClick event parameter (avoid implicit any).

-  const handleClick = (event) => {
+  const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
     event.preventDefault();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86f7b1b and 42b7e54.

⛔ Files ignored due to path filters (2)
  • packages/react-core/src/demos/assets/PF-IconLogo-color.svg is excluded by !**/*.svg
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (21)
  • packages/react-core/package.json (1 hunks)
  • packages/react-core/src/components/Compass/Compass.tsx (3 hunks)
  • packages/react-core/src/components/Compass/__tests__/Compass.test.tsx (1 hunks)
  • packages/react-core/src/components/Compass/examples/Compass.md (2 hunks)
  • packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1 hunks)
  • packages/react-core/src/components/Compass/examples/CompassDockLayout.tsx (1 hunks)
  • packages/react-core/src/components/Compass/examples/compass.css (1 hunks)
  • packages/react-core/src/components/Masthead/Masthead.tsx (2 hunks)
  • packages/react-core/src/components/Masthead/__tests__/Masthead.test.tsx (2 hunks)
  • packages/react-core/src/components/Nav/Nav.tsx (2 hunks)
  • packages/react-core/src/components/Nav/NavItem.tsx (4 hunks)
  • packages/react-core/src/components/Nav/__tests__/Nav.test.tsx (1 hunks)
  • packages/react-core/src/components/Page/Page.tsx (4 hunks)
  • packages/react-core/src/components/Page/__tests__/Page.test.tsx (1 hunks)
  • packages/react-core/src/components/Toolbar/Toolbar.tsx (3 hunks)
  • packages/react-core/src/demos/Page.md (3 hunks)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1 hunks)
  • packages/react-docs/package.json (1 hunks)
  • packages/react-icons/package.json (1 hunks)
  • packages/react-styles/package.json (1 hunks)
  • packages/react-tokens/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/react-tokens/package.json
  • packages/react-docs/package.json
  • packages/react-core/src/demos/Page.md
  • packages/react-core/src/components/Toolbar/Toolbar.tsx
  • packages/react-styles/package.json
  • packages/react-core/src/components/Compass/examples/Compass.md
  • packages/react-core/src/components/Nav/Nav.tsx
  • packages/react-core/src/components/Page/Page.tsx
🧰 Additional context used
🧬 Code graph analysis (7)
packages/react-core/src/components/Page/__tests__/Page.test.tsx (1)
packages/react-core/src/components/Page/Page.tsx (2)
  • render (227-364)
  • Page (367-367)
packages/react-core/src/components/Nav/__tests__/Nav.test.tsx (3)
packages/react-core/src/components/Nav/Nav.tsx (1)
  • Nav (174-174)
packages/react-core/src/components/Nav/NavList.tsx (1)
  • NavList (174-174)
packages/react-core/src/components/Nav/NavItem.tsx (1)
  • NavItem (285-287)
packages/react-core/src/components/Compass/examples/CompassDockLayout.tsx (5)
packages/react-core/src/components/Compass/CompassContent.tsx (1)
  • CompassContent (16-40)
packages/react-core/src/components/Compass/CompassMainHeader.tsx (1)
  • CompassMainHeader (27-52)
packages/react-core/src/components/Compass/CompassPanel.tsx (1)
  • CompassPanel (23-49)
packages/react-core/src/components/Compass/CompassMainHeaderContent.tsx (1)
  • CompassMainHeaderContent (15-23)
packages/react-core/src/components/Compass/Compass.tsx (1)
  • Compass (41-123)
packages/react-core/src/components/Compass/__tests__/Compass.test.tsx (1)
packages/react-core/src/components/Compass/Compass.tsx (1)
  • Compass (41-123)
packages/react-core/src/components/Compass/Compass.tsx (1)
packages/react-core/scripts/copyStyles.mjs (1)
  • css (32-32)
packages/react-core/src/components/Masthead/__tests__/Masthead.test.tsx (1)
packages/react-core/src/components/Masthead/Masthead.tsx (1)
  • Masthead (34-59)
packages/react-core/src/components/Nav/NavItem.tsx (2)
packages/react-core/src/components/Nav/Nav.tsx (1)
  • NavContext (63-63)
packages/react-core/src/components/Page/PageSidebar.tsx (1)
  • PageSidebarContext (28-28)
🪛 GitHub Check: Lint
packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx

[warning] 91-91:
React Hook useEffect has missing dependencies: 'handleClickOutside' and 'handleMenuKeys'. Either include them or remove the dependency array

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Unit tests
  • GitHub Check: Build demo app
  • GitHub Check: Build, test & deploy
🔇 Additional comments (12)
packages/react-icons/package.json (1)

37-37: LGTM! PatternFly version bump aligns with docked navigation feature.

The devDependency update from prerelease.27 to prerelease.32 is consistent with similar bumps across other packages in this PR and supports the new docked navigation styling.

packages/react-core/package.json (1)

57-57: LGTM! Consistent PatternFly version update.

This devDependency bump matches the version update across the monorepo and pulls in the necessary styling for docked navigation components.

packages/react-core/src/components/Masthead/Masthead.tsx (3)

30-31: LGTM! Well-designed variant prop with appropriate beta annotation.

The new variant prop follows established PatternFly patterns and is properly marked as @beta to signal its evolving nature.


41-41: Good default prevents breaking changes.

Setting variant = 'default' ensures backward compatibility for existing consumers.


49-49: LGTM! Conditional modifier class is correctly applied.

The docked modifier is applied only when variant === 'docked', following the standard PatternFly styling pattern.

packages/react-core/src/components/Nav/__tests__/Nav.test.tsx (1)

263-276: LGTM! Well-structured test for docked variant.

The test correctly verifies that the docked modifier class is applied when variant="docked". It follows the existing test patterns in the file and uses appropriate assertions.

packages/react-core/src/components/Compass/examples/CompassDockLayout.tsx (1)

1-25: LGTM! Clean example demonstrating dock prop usage.

This example correctly demonstrates passing ReactNode to the dock prop. The component structure is clear and appropriate for a basic demonstration.

packages/react-core/src/components/Masthead/__tests__/Masthead.test.tsx (1)

76-97: LGTM! Comprehensive test coverage for docked variant.

These three tests properly verify:

  1. Docked modifier is applied when variant="docked"
  2. Docked modifier is not applied when variant="default"
  3. Docked modifier is not applied when variant is omitted

The tests follow existing patterns and provide good coverage of the new functionality.

packages/react-core/src/components/Compass/examples/compass.css (1)

25-35: LGTM! Consistent demo styling for docked navigation.

This CSS block follows the established pattern in the file, providing visual layout boundaries for the docked navigation demo. The use of PatternFly design tokens and the :not([class*="footer"]) selector are both appropriate.

packages/react-core/src/components/Compass/Compass.tsx (3)

11-12: LGTM! Well-documented dock prop.

The dock prop is properly typed as React.ReactNode with clear documentation describing its purpose.


71-75: LGTM! Correct conditional rendering and modifier application.

The implementation properly:

  • Applies the dock modifier class when dock content is provided
  • Conditionally renders the dock container only when dock is defined
  • Uses the BEM-style class naming convention

76-108: LGTM! Conditional rendering improves efficiency.

The conversion to conditional rendering for all layout sections (header, sidebarStart, main, sidebarEnd, footer) is more efficient and semantically correct. The inert attribute logic is properly preserved within each conditional block.

Note: This is a minor behavioral change—previously these sections may have rendered empty containers. If any consumers relied on empty divs being present in the DOM, they would need to adjust. However, this change aligns with React best practices.

Copy link

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-core/src/demos/Page.md (1)

11-22: Remove duplicate imgAvatar import (line 21).

Line 11 and line 21 both import imgAvatar but from different paths, creating a naming collision that would cause a compilation error if copied into an actual component.

Apply this diff to remove the duplicate:

 import imgAvatar from '@patternfly/react-core/src/components/assets/avatarImg.svg';
 import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon';
 import AttentionBellIcon from '@patternfly/react-icons/dist/esm/icons/attention-bell-icon';
 import LightbulbIcon from '@patternfly/react-icons/dist/esm/icons/lightbulb-icon';
 import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
 import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon';
 import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon';
 import CloudIcon from '@patternfly/react-icons/dist/esm/icons/cloud-icon';
 import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon';
 import pfLogo from '@patternfly/react-core/src/demos/assets/PF-HorizontalLogo-Color.svg';
-import imgAvatar from '../../../components/assets/avatarImg.svg';
 import pfIconLogo from '../../assets/PF-IconLogo-color.svg';
🧹 Nitpick comments (2)
packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (2)

54-60: Make dropdown open/close handlers deterministic (avoid “toggle” everywhere)
Right now you toggle state in onDropdownToggle and onDropdownSelect while also controlling state via onOpenChange (Line 159). Prefer a functional toggle for clicks and an explicit close on select.

-  const onDropdownToggle = () => {
-    setIsDropdownOpen(!isDropdownOpen);
-  };
+  const onDropdownToggle = () => setIsDropdownOpen((prev) => !prev);
@@
-  const onDropdownSelect = () => {
-    setIsDropdownOpen(!isDropdownOpen);
-  };
+  const onDropdownSelect = () => setIsDropdownOpen(false);

Also applies to: 156-160


62-69: Simplify userDropdownItems (avoid array-of-fragment)
userDropdownItems is an array with a single fragment, then rendered into DropdownList (Line 62–68, Line 170). This is noisier than needed and can complicate keying later.

-  const userDropdownItems = [
-    <>
-      <DropdownItem key="group 2 profile">My profile</DropdownItem>
-      <DropdownItem key="group 2 user">User management</DropdownItem>
-      <DropdownItem key="group 2 logout">Logout</DropdownItem>
-    </>
-  ];
+  const userDropdownItems = (
+    <>
+      <DropdownItem key="profile">My profile</DropdownItem>
+      <DropdownItem key="user-management">User management</DropdownItem>
+      <DropdownItem key="logout">Logout</DropdownItem>
+    </>
+  );
@@
-                <DropdownList>{userDropdownItems}</DropdownList>
+                <DropdownList>{userDropdownItems}</DropdownList>

Also applies to: 170-170

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42b7e54 and 454aa8c.

📒 Files selected for processing (5)
  • packages/react-core/src/components/Compass/examples/Compass.md (2 hunks)
  • packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1 hunks)
  • packages/react-core/src/components/Nav/NavItem.tsx (7 hunks)
  • packages/react-core/src/demos/Page.md (3 hunks)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-core/src/components/Compass/examples/Compass.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build, test & deploy
🔇 Additional comments (9)
packages/react-core/src/components/Nav/NavItem.tsx (4)

1-11: Excellent fix to the type safety issues!

The imports now include forwardRef and useCallback, and the innerRef prop is properly typed as React.Ref<HTMLLIElement> instead of the previously unsafe React.Ref<any>. This addresses the critical type safety concern from the previous review.

Also applies to: 55-56


83-94: Perfect ref handling implementation!

All the critical ref handling issues from the previous review have been properly addressed:

  • flyoutRef now correctly initialized with null instead of undefined
  • The ref callback pattern safely handles both callback refs and object refs with a proper type guard
  • The forwarding logic ensures the DOM node is available both locally (via flyoutRef) and to the parent (via innerRef)

This implementation is type-safe and will not crash with either ref type.


95-97: Flyout visibility comparison correctly fixed!

The flyoutVisible logic now properly compares DOM nodes (flyoutRef?.current === contextFlyoutRefCurrent) instead of ref object identity. This addresses the critical comparison issue flagged in the previous review.


303-305: Critical prop spread order issue resolved!

The forwardRef wrapper now correctly spreads props first, then explicitly sets innerRef={ref}, ensuring the forwarded ref takes precedence over any innerRef that might be passed in props. This fixes the critical prop override vulnerability from the previous review.

The entire ref-forwarding architecture is now sound and follows React best practices.

packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (2)

42-43: LGTM: Relative imports correctly address previous feedback.

The asset imports now use relative paths instead of internal package paths, resolving the brittleness flagged in the previous review.


138-177: LGTM: Nav items now have unique IDs and itemIds.

Each NavItem correctly uses distinct id, to, itemId, and aria-label attributes, fixing the duplicate ID and selection issues from the previous review.

packages/react-core/src/demos/Page.md (1)

59-63: LGTM: Docked nav documentation section added appropriately.

The new "Docked nav" section correctly references the new demo component and fits well within the existing documentation structure.

packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (2)

136-139: Verify Tooltip + triggerRef behavior in this PF version
You’re rendering tooltips with triggerRef and no children (Line 136–139). Please confirm in the preview/a11y report that tooltips actually attach to the NavItem anchors and behave as expected (hover/focus).


50-52: Nav selection + itemId/isActive alignment looks correct now
Unique itemIds and matching isActive checks are consistent, and icon-only items have aria-labels.

Also applies to: 90-134

@@ -0,0 +1,196 @@
import { useRef, useState } from 'react';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix missing React identifier (likely TS compile error)
You reference React.FunctionComponent, React.FormEvent, and React.Ref, but React isn’t imported (Line 46, Line 50, Line 160). Prefer importing the needed types from react (or import * as React).

-import { useRef, useState } from 'react';
+import { useRef, useState, type FormEvent, type FunctionComponent, type Ref } from 'react';
@@
-export const CompassDockDemo: React.FunctionComponent = () => {
+export const CompassDockDemo: FunctionComponent = () => {
@@
-  const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
+  const onNavSelect = (_event: FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
     typeof selectedItem.itemId === 'number' && setActiveItem(selectedItem.itemId);
   };
@@
-                toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+                toggle={(toggleRef: Ref<MenuToggleElement>) => (
                   <MenuToggle
                     ref={toggleRef}

Also applies to: 46-52, 160-168


const mainContainerId = 'main-content-page-layout-tertiary-nav';

const handleClick = (event) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add type annotation for event parameter.

The event parameter lacks a type annotation.

Apply this diff:

-  const handleClick = (event) => {
+  const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
     event.preventDefault();
📝 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
const handleClick = (event) => {
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
🤖 Prompt for AI Agents
In packages/react-core/src/demos/examples/Page/PageDockedNav.tsx around line
225, the handleClick function's event parameter is missing a type; annotate it
(e.g. event: React.MouseEvent<HTMLElement> or the more specific element type
like HTMLButtonElement/HTMLAnchorElement) and ensure React is imported for the
type (or reference JSX types via global namespace). Update the function
signature to use the chosen React.MouseEvent generic so the event has proper
typing.

Copy link

@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

♻️ Duplicate comments (1)
packages/react-core/src/components/Nav/NavItem.tsx (1)

83-85: Critical: Unsafe ref handling will crash with callback refs.

Line 84 unsafely casts innerRef to MutableRefObject without checking if it's a callback function. When a callback ref is forwarded through the forwardRef wrapper (line 285), this cast will fail because callback refs don't have a .current property. The || _ref fallback logic is also broken since functions are truthy.

Additionally:

  • Line 83: Use null instead of undefined for ref initialization (TypeScript best practice)
  • Line 85: After fixing ref handling, compare .current values instead of ref object identity

Apply this pattern to safely handle both callback and object refs:

-  const _ref = useRef<HTMLLIElement>(undefined);
-  const ref = (innerRef as MutableRefObject<HTMLLIElement>) || _ref;
+  const _ref = useRef<HTMLLIElement | null>(null);
+  
+  const setRef = useCallback((node: HTMLLIElement | null) => {
+    _ref.current = node;
+    if (typeof innerRef === 'function') {
+      innerRef(node);
+    } else if (innerRef) {
+      (innerRef as MutableRefObject<HTMLLIElement | null>).current = node;
+    }
+  }, [innerRef]);

Then update line 267 and 247:

-        ref={ref}
+        ref={setRef}
-      triggerRef={ref}
+      triggerRef={_ref}

And line 85:

-  const flyoutVisible = ref === flyoutRef;
+  const flyoutVisible = _ref.current === flyoutRef?.current;
🧹 Nitpick comments (1)
packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1)

110-116: Remove unnecessary fragment wrapper.

The userDropdownItems array contains a single fragment wrapping the dropdown items. Since arrays can directly contain elements, the fragment is redundant.

Apply this diff:

  const userDropdownItems = [
-    <>
-      <DropdownItem key="group 2 profile">My profile</DropdownItem>
-      <DropdownItem key="group 2 user">User management</DropdownItem>
-      <DropdownItem key="group 2 logout">Logout</DropdownItem>
-    </>
+    <DropdownItem key="group 2 profile">My profile</DropdownItem>,
+    <DropdownItem key="group 2 user">User management</DropdownItem>,
+    <DropdownItem key="group 2 logout">Logout</DropdownItem>
  ];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 454aa8c and 36a8927.

📒 Files selected for processing (3)
  • packages/react-core/src/components/Nav/NavItem.tsx (4 hunks)
  • packages/react-core/src/demos/Page.md (3 hunks)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-core/src/demos/Page.md
🧰 Additional context used
🧬 Code graph analysis (1)
packages/react-core/src/components/Nav/NavItem.tsx (2)
packages/react-core/src/components/Nav/Nav.tsx (1)
  • NavContext (63-63)
packages/react-core/src/components/Page/PageSidebar.tsx (1)
  • PageSidebarContext (28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build, test & deploy

Copy link

@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: 0

♻️ Duplicate comments (3)
packages/react-core/src/components/Nav/NavItem.tsx (2)

1-11: Avoid importing MutableRefObject for unsafe casts; merge refs safely instead.
MutableRefObject is only used to force-cast innerRef, which breaks when innerRef is a callback ref (valid React.Ref case). Prefer a merged-ref callback and keep a stable local RefObject for internal logic/Popper.


55-56: Fix callback-ref crashes + incorrect useRef(undefined) + fragile flyout visibility check.
Current code will crash when innerRef is a callback ref (e.g., when NavItem is used via forwardRef), because it’s cast to MutableRefObject and later read via .current (Line 131 / Popper triggerRef). Also useRef<HTMLLIElement>(undefined) should be null, and flyoutVisible = ref === flyoutRef is fragile once refs are merged.

Proposed patch (keeps a stable internal ref object for Popper + context, while still forwarding to consumer ref safely):

 import {
   cloneElement,
   Fragment,
   isValidElement,
   useContext,
   useEffect,
+  useCallback,
   useRef,
   useState,
   forwardRef,
-  MutableRefObject
 } from 'react';
@@
   icon,
   innerRef,
   ...props
 }: NavItemProps) => {
   const { flyoutRef, setFlyoutRef, navRef } = useContext(NavContext);
@@
-  const _ref = useRef<HTMLLIElement>(undefined);
-  const ref = (innerRef as MutableRefObject<HTMLLIElement>) || _ref;
-  const flyoutVisible = ref === flyoutRef;
-  const popperRef = useRef<HTMLDivElement>(undefined);
+  const localRef = useRef<HTMLLIElement | null>(null);
+  const popperRef = useRef<HTMLDivElement | null>(null);
+
+  const setMergedRef = useCallback(
+    (node: HTMLLIElement | null) => {
+      localRef.current = node;
+      if (!innerRef) return;
+      if (typeof innerRef === 'function') {
+        innerRef(node);
+      } else {
+        (innerRef as React.MutableRefObject<HTMLLIElement | null>).current = node;
+      }
+    },
+    [innerRef]
+  );
+
+  const flyoutVisible = !!localRef.current && flyoutRef?.current === localRef.current;
@@
-      setFlyoutRef(ref);
+      setFlyoutRef(localRef);
@@
-    } else if ((flyoutVisible || override) && !show) {
+    } else if ((flyoutVisible || override) && !show) {
       setFlyoutRef(null);
     }
@@
-    if ((key === ' ' || key === 'Enter' || key === 'ArrowRight') && hasFlyout && ref?.current?.contains(target)) {
+    if (
+      (key === ' ' || key === 'Enter' || key === 'ArrowRight') &&
+      hasFlyout &&
+      localRef.current?.contains(target)
+    ) {
@@
   const flyoutPopper = (
     <Popper
-      triggerRef={ref}
+      triggerRef={localRef}
       popper={
         <div ref={popperRef} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
           {flyout}
         </div>
       }
       popperRef={popperRef}
@@
   const navItem = (
     <>
       <li
         onMouseOver={onMouseOver}
         className={css(styles.navItem, hasFlyout && styles.modifiers.flyout, className)}
-        ref={ref}
+        ref={setMergedRef}
         {...ouiaProps}
       >

Note: this assumes NavContextProps['flyoutRef'] is a RefObject<HTMLLIElement> | null (or compatible). If it’s currently MutableRefObject<HTMLLIElement>, it should be widened to RefObject<HTMLLIElement> and use current comparisons consistently.

Also applies to: 83-86, 131-138, 245-260, 264-268

packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1)

1-1: Fix missing React identifier (TypeScript compile error).

React.FunctionComponent (line 46), React.FormEvent (line 50), and React.Ref (line 160) reference the React namespace, but only named exports are imported. This will fail to compile.

-import { useRef, useState } from 'react';
+import React, { useRef, useState } from 'react';

Alternatively, import only the needed types:

-import { useRef, useState } from 'react';
+import { useRef, useState, type FunctionComponent, type FormEvent, type Ref } from 'react';

Then update usages accordingly (e.g., FunctionComponent instead of React.FunctionComponent).

🧹 Nitpick comments (1)
packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1)

62-68: Consider simplifying the dropdown items declaration.

Wrapping a fragment inside a single-element array is an unusual pattern that could trigger React key warnings. If DropdownList expects children, a direct fragment or inline children would be cleaner.

-  const userDropdownItems = [
-    <>
-      <DropdownItem key="group 2 profile">My profile</DropdownItem>
-      <DropdownItem key="group 2 user">User management</DropdownItem>
-      <DropdownItem key="group 2 logout">Logout</DropdownItem>
-    </>
-  ];
+  const userDropdownItems = (
+    <>
+      <DropdownItem key="group 2 profile">My profile</DropdownItem>
+      <DropdownItem key="group 2 user">User management</DropdownItem>
+      <DropdownItem key="group 2 logout">Logout</DropdownItem>
+    </>
+  );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e178dfc and add8809.

📒 Files selected for processing (3)
  • packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1 hunks)
  • packages/react-core/src/components/Nav/NavItem.tsx (4 hunks)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/react-core/src/components/Nav/NavItem.tsx (2)
packages/react-core/src/components/Nav/Nav.tsx (1)
  • NavContext (63-63)
packages/react-core/src/components/Page/PageSidebar.tsx (1)
  • PageSidebarContext (28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build, test & deploy
🔇 Additional comments (4)
packages/react-core/src/components/Nav/NavItem.tsx (1)

285-287: forwardRef<HTMLLIElement, NavItemProps> wrapper looks correct; prop spread order is safe.
<NavItemBase {...props} innerRef={ref} /> ensures the forwarded ref can’t be overridden by a consumer-provided innerRef (even if someone uses it despite @hide).

packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (3)

88-139: NavItem configuration and tooltips look correct.

The itemId values (0, 1, 2, 3) now correctly match their corresponding isActive checks, and all icon-only NavItems have appropriate aria-label attributes. The ref-based Tooltip wiring is properly implemented.


156-173: User menu dropdown is properly accessible.

The MenuToggle now includes aria-label="User menu" for the icon-only toggle, addressing the accessibility concern.


189-196: Component structure is well-organized.

The Compass component properly receives dock and main content, demonstrating the new docked navigation layout feature as intended by the PR objectives.

Copy link

@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: 0

🧹 Nitpick comments (1)
packages/react-integration/cypress/integration/drawer.spec.ts (1)

49-50: Make this flex-basis assertion tolerant to equivalent CSS normalizations (avoid brittle exact-string match).

min(100% + 0px, 100%) is semantically equivalent to min(100%, 100%), but different engines/versions may stringify these differently, causing flaky runs.

Suggested tweak (accept either form):

 cy.viewport(800, 660);
-cy.get('#basic-drawer .pf-v6-c-drawer__panel').should('have.css', 'flex-basis', 'max(24px, min(100% + 0px, 100%))');
+const mediumFlexBasis = new Set([
+  'max(24px, min(100%, 100%))',
+  'max(24px, min(100% + 0px, 100%))'
+]);
+cy.get('#basic-drawer .pf-v6-c-drawer__panel')
+  .invoke('css', 'flex-basis')
+  .then((v) => expect(mediumFlexBasis.has(v), `unexpected flex-basis: ${v}`).to.eq(true));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e10bdd and 2678d65.

📒 Files selected for processing (1)
  • packages/react-integration/cypress/integration/drawer.spec.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build, test & deploy

Copy link
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.

A couple of nav items aren't updating as "current" correctly, and the logo should be 37px, which matches the default button/form element size.

Copy link
Collaborator

@andrew-ronaldson andrew-ronaldson left a comment

Choose a reason for hiding this comment

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

Visually good but defer to the dev reviewers

Copy link

@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: 0

♻️ Duplicate comments (1)
packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1)

1-1: Prefer imported React types over global React.* namespace

You’re using React.FunctionComponent, React.FormEvent, and React.Ref without importing the React identifier. This relies on the global React namespace and can break under stricter TS configs or future typings. It’s safer and clearer to import the needed types directly from react and drop the React. prefix.

Example refactor:

-import { useRef, useState } from 'react';
+import { useRef, useState, type FormEvent, type FunctionComponent, type Ref } from 'react';

-export const CompassDockDemo: React.FunctionComponent = () => {
+export const CompassDockDemo: FunctionComponent = () => {
@@
-  const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
-    typeof selectedItem.itemId === 'number' && setActiveItem(selectedItem.itemId);
-  };
+  const onNavSelect = (_event: FormEvent<HTMLElement>, selectedItem: NavOnSelectProps) => {
+    if (typeof selectedItem.itemId === 'number') {
+      setActiveItem(selectedItem.itemId);
+    }
+  };
@@
-                toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+                toggle={(toggleRef: Ref<MenuToggleElement>) => (

This keeps everything type-safe, avoids depending on a global React, and addresses the earlier “missing React identifier” concern in a more robust way.

Also applies to: 46-52, 160-168

🧹 Nitpick comments (1)
packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1)

40-52: Reuse the library NavOnSelectProps type instead of redefining it

The local NavOnSelectProps interface is structurally fine, but it shadows the PatternFly type of the same name and can drift from upstream if that type changes. Prefer importing the type from @patternfly/react-core (or deriving from NavProps['onSelect']) so your handler stays aligned with the component’s official signature.

Sketch:

-import {
-  Compass,
-  ...
-  Nav,
-  ...
-} from '@patternfly/react-core';
+import {
+  Compass,
+  ...
+  Nav,
+  type NavOnSelectProps,
+  ...
+} from '@patternfly/react-core';
-
-interface NavOnSelectProps {
-  groupId: number | string;
-  itemId: number | string;
-  to: string;
-}
+// Use the library NavOnSelectProps so this stays in sync with Nav

If for some reason your current PatternFly version does not export NavOnSelectProps, the existing local interface is acceptable; this is mainly about future-proofing and consistency with the rest of the codebase.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2678d65 and 6c019f2.

📒 Files selected for processing (2)
  • packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1 hunks)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Unit tests
  • GitHub Check: Build demo app
  • GitHub Check: Build, test & deploy
🔇 Additional comments (1)
packages/react-core/src/components/Compass/examples/CompassDockDemo.tsx (1)

70-177: Docked masthead/nav structure and accessibility look solid

The overall docked masthead + vertical toolbar/nav composition looks good:

  • Nav item itemId and isActive are now consistent (0–3), so selection state behaves correctly.
  • useRef + triggerRef wiring for the four tooltips is clean and idiomatic.
  • Icon-only controls have accessible names (aria-label on NavItems, settings/help buttons, and the user MenuToggle), which aligns well with a11y expectations for a docked, icon-only layout.

No functional or accessibility issues stand out in this block.

Copy link

@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

♻️ Duplicate comments (1)
packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1)

1-1: Import and use React types correctly (current usage will not compile).

React.FunctionComponent, React.FormEvent, and React.Ref are used without importing React or those types, which will cause TS compile errors. Prefer importing the needed types from react and dropping the React. prefix.

Suggested fix:

-import { useRef, useState } from 'react';
+import { useRef, useState, type FormEvent, type FunctionComponent, type Ref } from 'react';

-export const CompassDockDemo: React.FunctionComponent = () => {
+export const CompassDockDemo: FunctionComponent = () => {

-  const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
+  const onNavSelect = (_event: FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {

-                toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+                toggle={(toggleRef: Ref<MenuToggleElement>) => (

Please confirm this matches the repo’s preferred style (named type imports vs. import * as React).

Also applies to: 46-52, 160-169

🧹 Nitpick comments (1)
packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1)

54-60: Simplify dropdown open/close logic to avoid double-toggling and stale state (optional).

The dropdown state is toggled using !isDropdownOpen in multiple handlers while also controlled via onOpenChange. This works but is slightly brittle; using a functional updater and closing explicitly on select is clearer.

Consider:

-  const onDropdownToggle = () => {
-    setIsDropdownOpen(!isDropdownOpen);
-  };
-
-  const onDropdownSelect = () => {
-    setIsDropdownOpen(!isDropdownOpen);
-  };
+  const onDropdownToggle = () => {
+    setIsDropdownOpen(prev => !prev);
+  };
+
+  const onDropdownSelect = () => {
+    setIsDropdownOpen(false);
+  };

This keeps the component fully controlled and avoids any race with onOpenChange. Please confirm this matches how Dropdown is expected to be used in the current PatternFly docs.

Also applies to: 155-172

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ded5c6c and 72833ae.

📒 Files selected for processing (3)
  • packages/react-core/src/components/Compass/examples/Compass.md (2 hunks)
  • packages/react-core/src/demos/Compass/Compass.md (2 hunks)
  • packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-core/src/components/Compass/examples/Compass.md
🧰 Additional context used
🧬 Code graph analysis (1)
packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (19)
packages/react-core/src/components/Dropdown/DropdownItem.tsx (1)
  • DropdownItem (68-72)
packages/react-core/src/components/Masthead/Masthead.tsx (1)
  • Masthead (34-59)
packages/react-core/src/components/Masthead/MastheadLogo.tsx (1)
  • MastheadLogo (14-33)
packages/react-core/src/components/Brand/Brand.tsx (1)
  • Brand (37-82)
packages/react-core/src/components/Divider/Divider.tsx (1)
  • Divider (38-60)
packages/react-core/src/components/Toolbar/Toolbar.tsx (1)
  • Toolbar (226-226)
packages/react-core/src/components/Toolbar/ToolbarContent.tsx (1)
  • ToolbarContent (127-127)
packages/react-core/src/components/Nav/Nav.tsx (1)
  • Nav (174-174)
packages/react-core/src/components/Nav/NavList.tsx (1)
  • NavList (174-174)
packages/react-core/src/components/Nav/NavItem.tsx (1)
  • NavItem (285-287)
packages/react-core/src/components/Tooltip/Tooltip.tsx (1)
  • Tooltip (142-373)
packages/react-core/src/components/Toolbar/ToolbarGroup.tsx (1)
  • ToolbarGroup (237-239)
packages/react-core/src/components/Dropdown/Dropdown.tsx (1)
  • Dropdown (215-217)
packages/react-core/src/components/MenuToggle/MenuToggle.tsx (2)
  • MenuToggleElement (23-23)
  • MenuToggle (246-248)
packages/react-core/src/components/Avatar/Avatar.tsx (1)
  • Avatar (18-32)
packages/react-core/src/components/Dropdown/DropdownList.tsx (1)
  • DropdownList (11-19)
packages/react-core/src/components/Compass/CompassContent.tsx (1)
  • CompassContent (16-40)
packages/react-core/src/components/Compass/CompassPanel.tsx (1)
  • CompassPanel (23-49)
packages/react-core/src/components/Compass/Compass.tsx (1)
  • Compass (41-123)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build, test & deploy
🔇 Additional comments (2)
packages/react-core/src/demos/Compass/Compass.md (2)

36-40: New demo section is properly structured and follows existing patterns.

The "Docked nav demo" section is well-formatted and consistent with the existing "Compass layout" demo. Ensure the referenced CompassDockDemo.tsx file exists (verified in the previous check).

The verification script above will confirm that CompassDockDemo.tsx exists.


12-18: All imported icons and assets are properly used in the referenced example files. The verification confirms:

  • CubeIcon, FolderIcon, QuestionCircleIcon, CloudIcon, and CodeIcon are all imported and actively used in CompassDockDemo.tsx
  • imgAvatar and pfLogo asset imports are both used in CompassDockDemo.tsx
  • All asset files (avatarImg.svg and PF-IconLogo-color.svg) exist at the expected locations

No issues found with the imports or asset references.

Comment on lines +70 to +73
const navItem1Ref = useRef<HTMLAnchorElement>(null);
const navItem2Ref = useRef<HTMLAnchorElement>(null);
const navItem3Ref = useRef<HTMLAnchorElement>(null);
const navItem4Ref = useRef<HTMLAnchorElement>(null);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine NavItem.tsx
find . -name "NavItem.tsx" -type f

Repository: patternfly/patternfly-react

Length of output: 123


🏁 Script executed:

# Find the CompassDockDemo.tsx file and check its location
find . -name "CompassDockDemo.tsx" -type f

Repository: patternfly/patternfly-react

Length of output: 139


🏁 Script executed:

# Search for Tooltip component definition
find . -name "Tooltip.tsx" -o -name "Tooltip.ts" -o -name "Tooltip.d.ts" | head -20

Repository: patternfly/patternfly-react

Length of output: 127


🏁 Script executed:

cat -n ./packages/react-core/src/components/Nav/NavItem.tsx | head -100

Repository: patternfly/patternfly-react

Length of output: 4793


🏁 Script executed:

cat -n ./packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx | sed -n '65,145p'

Repository: patternfly/patternfly-react

Length of output: 3796


🏁 Script executed:

cat -n ./packages/react-core/src/components/Tooltip/Tooltip.tsx | head -150

Repository: patternfly/patternfly-react

Length of output: 7072


🏁 Script executed:

cat -n ./packages/react-core/src/components/Nav/NavItem.tsx | sed -n '100,250p'

Repository: patternfly/patternfly-react

Length of output: 5613


🏁 Script executed:

# Check the export at the end of NavItem.tsx
tail -50 ./packages/react-core/src/components/Nav/NavItem.tsx

Repository: patternfly/patternfly-react

Length of output: 1253


Fix NavItem ref types to match HTMLLIElement forward.

NavItem forwards its ref as HTMLLIElement (via forwardRef<HTMLLIElement, NavItemProps>), but the refs here are incorrectly typed as HTMLAnchorElement. Change to HTMLLIElement | null to align with the actual DOM element being referenced:

-  const navItem1Ref = useRef<HTMLAnchorElement>(null);
-  const navItem2Ref = useRef<HTMLAnchorElement>(null);
-  const navItem3Ref = useRef<HTMLAnchorElement>(null);
-  const navItem4Ref = useRef<HTMLAnchorElement>(null);
+  const navItem1Ref = useRef<HTMLLIElement | null>(null);
+  const navItem2Ref = useRef<HTMLLIElement | null>(null);
+  const navItem3Ref = useRef<HTMLLIElement | null>(null);
+  const navItem4Ref = useRef<HTMLLIElement | null>(null);

This applies to lines 70–73 and also to lines 90–139 where these refs are used.

📝 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
const navItem1Ref = useRef<HTMLAnchorElement>(null);
const navItem2Ref = useRef<HTMLAnchorElement>(null);
const navItem3Ref = useRef<HTMLAnchorElement>(null);
const navItem4Ref = useRef<HTMLAnchorElement>(null);
const navItem1Ref = useRef<HTMLLIElement | null>(null);
const navItem2Ref = useRef<HTMLLIElement | null>(null);
const navItem3Ref = useRef<HTMLLIElement | null>(null);
const navItem4Ref = useRef<HTMLLIElement | null>(null);
🤖 Prompt for AI Agents
In packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx around
lines 70–73 (and also where those refs are used around lines 90–139), the
NavItem refs are typed as HTMLAnchorElement but NavItem.forwardRef returns an
HTMLLIElement; change the useRef types to useRef<HTMLLIElement | null>(null)
(and update any other declarations/usages of these refs within lines 90–139 to
reflect HTMLLIElement | null) so the ref types match the forwarded element and
TypeScript no longer errors.

Copy link

@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

♻️ Duplicate comments (1)
packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1)

1-2: Prefer explicit React type imports over React.* namespace usage

You’re using React.FunctionComponent, React.FormEvent, and React.Ref without importing React or these types. This often relies on implicit global React typings and can break with stricter TS/react configs. Importing the types directly is clearer and more robust.

Suggested diff:

-import { useRef, useState } from 'react';
+import { useRef, useState, type FormEvent, type FunctionComponent, type Ref } from 'react';

-export const CompassDockDemo: React.FunctionComponent = () => {
+export const CompassDockDemo: FunctionComponent = () => {
@@
-  const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
+  const onNavSelect = (_event: FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
@@
-                toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
+                toggle={(toggleRef: Ref<MenuToggleElement>) => (

Please confirm this aligns with your repo’s TS/React version and style.

Also applies to: 46-52, 160-168

🧹 Nitpick comments (2)
packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1)

54-60: Use functional state updates and a shared dropdown toggle handler

Both dropdown handlers flip the same boolean state using the current closure value, which is slightly brittle under concurrent rendering and duplicates logic. A small refactor improves correctness and readability.

-  const onDropdownToggle = () => {
-    setIsDropdownOpen(!isDropdownOpen);
-  };
-
-  const onDropdownSelect = () => {
-    setIsDropdownOpen(!isDropdownOpen);
-  };
+  const toggleDropdown = () => {
+    setIsDropdownOpen(prevIsOpen => !prevIsOpen);
+  };
+
+  const onDropdownToggle = toggleDropdown;
+
+  const onDropdownSelect = toggleDropdown;
packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1)

78-84: Simplify array structure by removing unnecessary fragment.

The single fragment wrapping all dropdown items is unnecessary. The array can directly contain the elements.

Apply this diff:

  const userDropdownItems = [
-    <>
-      <DropdownItem key="group 2 profile">My profile</DropdownItem>
-      <DropdownItem key="group 2 user">User management</DropdownItem>
-      <DropdownItem key="group 2 logout">Logout</DropdownItem>
-    </>
+    <DropdownItem key="group 2 profile">My profile</DropdownItem>,
+    <DropdownItem key="group 2 user">User management</DropdownItem>,
+    <DropdownItem key="group 2 logout">Logout</DropdownItem>
  ];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72833ae and 860f035.

📒 Files selected for processing (3)
  • packages/react-core/src/components/Nav/NavItem.tsx (6 hunks)
  • packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1 hunks)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (24)
packages/react-core/src/components/Breadcrumb/Breadcrumb.tsx (1)
  • Breadcrumb (19-42)
packages/react-core/src/components/Dropdown/DropdownItem.tsx (1)
  • DropdownItem (68-72)
packages/react-core/src/components/Masthead/Masthead.tsx (1)
  • Masthead (34-59)
packages/react-core/src/components/Masthead/MastheadBrand.tsx (1)
  • MastheadBrand (11-19)
packages/react-core/src/components/Masthead/MastheadLogo.tsx (1)
  • MastheadLogo (14-33)
packages/react-core/src/components/Brand/Brand.tsx (1)
  • Brand (37-82)
packages/react-core/src/components/Divider/Divider.tsx (1)
  • Divider (38-60)
packages/react-core/src/components/Toolbar/Toolbar.tsx (1)
  • Toolbar (226-226)
packages/react-core/src/components/Toolbar/ToolbarContent.tsx (1)
  • ToolbarContent (127-127)
packages/react-core/src/components/Toolbar/ToolbarItem.tsx (1)
  • ToolbarItem (173-237)
packages/react-core/src/components/Nav/Nav.tsx (1)
  • Nav (174-174)
packages/react-core/src/components/Nav/NavList.tsx (1)
  • NavList (174-174)
packages/react-core/src/components/Tooltip/Tooltip.tsx (1)
  • Tooltip (142-373)
packages/react-core/src/components/Dropdown/Dropdown.tsx (1)
  • Dropdown (215-217)
packages/react-core/src/components/MenuToggle/MenuToggle.tsx (2)
  • MenuToggleElement (23-23)
  • MenuToggle (246-248)
packages/react-core/src/components/Avatar/Avatar.tsx (1)
  • Avatar (18-32)
packages/react-core/src/components/Dropdown/DropdownList.tsx (1)
  • DropdownList (11-19)
packages/react-core/src/components/SkipToContent/SkipToContent.tsx (1)
  • SkipToContent (14-25)
packages/react-core/src/components/Page/Page.tsx (1)
  • Page (367-367)
packages/react-core/src/components/Page/PageSection.tsx (1)
  • PageSection (85-137)
packages/react-core/src/components/Content/Content.tsx (1)
  • Content (85-118)
packages/react-core/src/layouts/Gallery/Gallery.tsx (1)
  • Gallery (34-72)
packages/react-core/src/components/Card/Card.tsx (1)
  • Card (155-155)
packages/react-core/src/components/Card/CardBody.tsx (1)
  • CardBody (17-30)
packages/react-core/src/components/Nav/NavItem.tsx (2)
packages/react-core/src/components/Nav/Nav.tsx (1)
  • NavContext (63-63)
packages/react-core/src/components/Page/PageSidebar.tsx (1)
  • PageSidebarContext (28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build, test & deploy
🔇 Additional comments (2)
packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (2)

70-139: Icon-only docked nav items + tooltips look correct and accessible

The docked Nav wiring with anchorRef and Tooltip plus per-item aria-labels is coherent: IDs, itemId/isActive values, and tooltip trigger refs all line up and give each icon-only item an accessible name. No issues from my side here.


189-195: Compass dock/main composition cleanly demonstrates the docked layout

The Compass usage with dock={dockContent} and main={mainContent} plus background assets is straightforward and clearly showcases the new docked nav layout.

Comment on lines +86 to 88
const _ref = useRef<HTMLLIElement>(undefined);
const ref = (innerRef as MutableRefObject<HTMLLIElement>) || _ref;
const flyoutVisible = ref === flyoutRef;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical ref handling issue remains unaddressed—callback refs will silently fail.

The previous critical review flagged unsafe ref handling, but the core runtime logic was not fixed:

  1. Line 86: useRef<HTMLLIElement>(undefined) should initialize with null (React convention).

  2. Line 87: Unsafe cast of innerRef as MutableRefObject<HTMLLIElement> assumes innerRef has a .current property. However, React.Ref<T> can be a callback function (node: T | null) => void, which has no .current. When a callback ref is passed from a parent (valid usage), line 134's ref?.current?.contains(target) will evaluate to undefined, silently breaking flyout keyboard navigation and hover detection.

  3. Line 88: Comparing ref objects by identity (ref === flyoutRef) instead of comparing DOM nodes (ref?.current === flyoutRef?.current) can fail if ref objects change between renders.

Apply this fix to safely handle both callback and object refs:

-  const _ref = useRef<HTMLLIElement>(undefined);
-  const ref = (innerRef as MutableRefObject<HTMLLIElement>) || _ref;
-  const flyoutVisible = ref === flyoutRef;
+  const _ref = useRef<HTMLLIElement>(null);
+  const ref = _ref;
+  
+  // Safely forward to both innerRef and local ref
+  const setRef = useCallback((node: HTMLLIElement | null) => {
+    _ref.current = node;
+    if (typeof innerRef === 'function') {
+      innerRef(node);
+    } else if (innerRef) {
+      (innerRef as MutableRefObject<HTMLLIElement>).current = node;
+    }
+  }, [innerRef]);
+  
+  const flyoutVisible = flyoutRef?.current === ref?.current;

Then update line 272:

-        ref={ref}
+        ref={setRef}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/react-core/src/components/Nav/NavItem.tsx around lines 86-88 (and
also update usage at line 272), change the ref handling to follow React
conventions: initialize the internal ref with null
(useRef<HTMLLIElement>(null)), do NOT cast innerRef to MutableRefObject; instead
create a small utility that normalizes a React.Ref to a DOM node (supporting
both object refs with .current and callback refs) and use that to derive the
current node; replace the identity comparison ref === flyoutRef with a node
comparison (normalize(innerRef) === normalize(flyoutRef) or
normalize(ref)?.contains(...) where appropriate) so all checks compare DOM nodes
and the contains call uses the actual element, ensuring callback refs and object
refs both work correctly.

@@ -0,0 +1,242 @@
import { useRef, useState } from 'react';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add missing React import for type references.

The code references React.FunctionComponent (line 51), React.FormEvent (line 55), React.Ref (line 173), and React.MouseEvent (line 195) without importing React. This will cause a TypeScript compilation error.

Apply this diff:

-import { useRef, useState } from 'react';
+import React, { useRef, useState } from 'react';
📝 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
import { useRef, useState } from 'react';
import React, { useRef, useState } from 'react';
🤖 Prompt for AI Agents
In packages/react-core/src/demos/examples/Page/PageDockedNav.tsx around line 1,
the file uses React types (React.FunctionComponent, React.FormEvent, React.Ref,
React.MouseEvent) but only imports useRef and useState; add a React import so
the type names resolve. Update the top-level imports to include React (for
example import * as React from 'react' or import React from 'react') or
alternatively add explicit type-only imports (import type { FunctionComponent,
FormEvent, Ref, MouseEvent } from 'react') so TypeScript can compile the
referenced types.

Copy link

@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: 0

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

223-240: Consider using template literal for consistency.

The test logic is correct and provides good coverage for the isVertical prop. However, for consistency with other modifier tests (lines 46, 60, 209), consider using template literals (backticks) instead of single quotes in the test description.

Apply this diff to align with the established pattern:

-  it('Renders with class ${styles.modifiers.vertical} when isVertical is true', () => {
+  it(`Renders with class ${styles.modifiers.vertical} when isVertical is true`, () => {

This ensures the test output displays the actual CSS class name rather than the literal text.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 860f035 and e09d1b6.

📒 Files selected for processing (1)
  • packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx (1)
packages/react-core/src/components/Toolbar/ToolbarGroup.tsx (1)
  • render (178-234)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build, test & deploy
🔇 Additional comments (2)
packages/react-core/src/components/Toolbar/__tests__/Toolbar.test.tsx (2)

57-57: Good improvement for maintainability.

Referencing the modifier via styles.modifiers.noPadding instead of a hardcoded string is more maintainable and consistent with the pattern used elsewhere in the test file.


209-221: LGTM! Well-structured test for default behavior.

The test correctly verifies that the vertical modifier is not applied by default and follows the established patterns in the file.

Copy link

@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: 0

♻️ Duplicate comments (3)
packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (2)

41-42: Avoid importing demo assets via @patternfly/react-core/src/...; use relative paths.

These imports reach into the package’s internal src structure, which is brittle and may break with bundler/module-resolution changes or when the package layout shifts. Prefer relative imports from the demo assets directory instead.

For example:

-import imgAvatar from '@patternfly/react-core/src/components/assets/avatarImg.svg';
-import pfIconLogo from '@patternfly/react-core/src/demos/assets/PF-IconLogo-color.svg';
+import imgAvatar from '../../../components/assets/avatarImg.svg';
+import pfIconLogo from '../../assets/PF-IconLogo-color.svg';

1-1: Import React (or React types) when using the React.* namespace.

This file uses React.FunctionComponent, React.FormEvent, and React.MouseEvent but only imports hooks. Unless your TS config intentionally exposes a global React namespace, this can cause type errors; it’s safer to explicitly import React (or the relevant types) from 'react'.

For example:

-import { useRef, useState } from 'react';
+import React, { useRef, useState } from 'react';

Or, if you prefer type-only imports, adjust the annotations and add:

import type { FunctionComponent, FormEvent, MouseEvent } from 'react';
#!/bin/bash
# Compare with other demos that use React.FunctionComponent to ensure consistency.
rg -n "React\\.FunctionComponent" packages/react-core/src/demos/examples -C2
packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1)

1-68: Import React types explicitly and reuse them in signatures.

You’re using React.FunctionComponent and React.FormEvent without importing React or those types. In modern TS/React setups this often relies on ambient globals and can break with stricter configs or future React typings. Prefer importing the types and using them directly.

For example:

-import { useRef, useState } from 'react';
+import { useRef, useState, type FormEvent, type FunctionComponent } from 'react';
@@
-export const CompassDockDemo: React.FunctionComponent = () => {
+export const CompassDockDemo: FunctionComponent = () => {
@@
-  const onNavSelect = (_event: React.FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
+  const onNavSelect = (_event: FormEvent<HTMLInputElement>, selectedItem: NavOnSelectProps) => {
     typeof selectedItem.itemId === 'number' && setActiveItem(selectedItem.itemId);
   };

Optionally, if NavOnSelectProps (or an equivalent) is exported from @patternfly/react-core, consider importing that instead of re-declaring the interface here to avoid future drift.

🧹 Nitpick comments (2)
packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1)

44-55: Consolidate the local NavOnSelectProps interface to avoid code duplication across demos.

PatternFly's Nav component doesn't export a NavOnSelectProps type—only an inline object shape within NavProps. The event type (React.FormEvent<HTMLInputElement>) is correct and matches the library's actual signature. However, this interface is redeclared identically across at least 8 demo files (PageDockedNav, PageStickySectionGroup, PageStickySectionGroupAlternate, PageStickySectionBreadcrumb, PageContextSelector, MastheadWithUtilitiesAndUserDropdownMenu, MastheadWithHorizontalNav, CompassDockDemo).

Rather than importing (no export exists), consolidate by:

  • Creating a shared types file in the demos directory and importing from there, or
  • Removing the interface and inlining the type where needed

This reduces duplication and maintenance burden if the Nav component's shape evolves.

packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1)

167-190: Simplify Dropdown open-state handling to rely on onOpenChange only.

Right now the user menu dropdown is controlled in two places:

  • onOpenChange={(isOpen: boolean) => setIsDropdownOpen(isOpen)}
  • MenuToggle’s onClick={onDropdownToggle} which also flips isDropdownOpen.

Depending on how PatternFly wires MenuToggle’s click handler, this dual control can lead to redundant or conflicting state updates. You can let Dropdown own the toggle logic and treat onOpenChange as the single source of truth:

-  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
-
-  const onDropdownToggle = () => {
-    setIsDropdownOpen((prevIsOpen) => !prevIsOpen);
-  };
+  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@@
-              <Dropdown
-                isOpen={isDropdownOpen}
-                shouldFocusToggleOnSelect
-                onSelect={onDropdownSelect}
-                onOpenChange={(isOpen: boolean) => setIsDropdownOpen(isOpen)}
+              <Dropdown
+                isOpen={isDropdownOpen}
+                shouldFocusToggleOnSelect
+                onSelect={onDropdownSelect}
+                onOpenChange={setIsDropdownOpen}
@@
-                      <MenuToggle
-                        ref={userMenuRef}
-                        onClick={onDropdownToggle}
-                        isExpanded={isDropdownOpen}
+                      <MenuToggle
+                        ref={userMenuRef}
+                        isExpanded={isDropdownOpen}
                         icon={<Avatar src={imgAvatar} alt="" size="sm" />}
                         variant="plain"
                         aria-label="User menu"

This keeps the open state fully controlled via the PatternFly Dropdown’s onOpenChange callback and avoids potential double-toggles, while still allowing onDropdownSelect to explicitly close on selection.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e09d1b6 and a682a09.

📒 Files selected for processing (2)
  • packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (1 hunks)
  • packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build, test & deploy
🔇 Additional comments (4)
packages/react-core/src/demos/examples/Page/PageDockedNav.tsx (1)

227-257: Page layout, docked masthead, and skip-to-content wiring look solid.

Page with variant="docked", the composed masthead, skipToContent tied to mainContainerId, and the card gallery content all follow expected PatternFly layout and a11y patterns. Nothing blocking here.

packages/react-core/src/demos/Compass/examples/CompassDockDemo.tsx (3)

69-143: Nav item refs, selection, and a11y look solid for the docked variant.

The docked Nav block is wired cleanly:

  • itemId values (0–3) now correctly align with activeItem comparisons and the onNavSelect handler.
  • Using anchorRef={navItemXRef} plus Tooltip triggerRef={navItemXRef} is a good way to keep icon-only items focusable with hover/focus hints, while aria-label on each NavItem provides the accessible name.

No changes needed here from a behavior or accessibility standpoint.


144-166: Settings/help toolbar icons are accessible and responsive.

The settings and help buttons:

  • Have explicit aria-labels, which is important for icon-only controls.
  • Are wrapped in tooltips without overloading the accessible name.
  • Use visibility={{ default: 'hidden', lg: 'visible' }} to avoid cluttering smaller viewports.

This section looks good as-is.


198-215: Compass dock/main wiring matches the new docked layout API.

Splitting the layout into dockContent and mainContent and passing them into Compass as dock and main props is a clean use of the new API and clearly demonstrates the docked navigation pattern. Background image usage is consistent for light/dark variants.

No changes needed here.

Copy link
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.

LGTM re: the updates made from my comments

(and obviously the updates I made look good to me 😎 )

Copy link
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.

🤖👍
bleep bloop!!

@mcoker mcoker merged commit 5519389 into patternfly:main Dec 15, 2025
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.

Navigation: React support to enable docked nav variant

5 participants