Skip to content

fix: sidebar collapse/expand toggle not working after first interaction#72

Merged
kzamanbd merged 4 commits intomainfrom
fix/sidebar-collapse-toggle
Apr 1, 2026
Merged

fix: sidebar collapse/expand toggle not working after first interaction#72
kzamanbd merged 4 commits intomainfrom
fix/sidebar-collapse-toggle

Conversation

@kzamanbd
Copy link
Copy Markdown
Contributor

@kzamanbd kzamanbd commented Mar 27, 2026

Summary

  • Replaced Base-UI Collapsible component with React state-driven conditional rendering in LayoutMenu
  • The Collapsible.Trigger render prop chain through SidebarMenuButton's Tooltip wrapper broke event handling and animation state tracking after the first expand cycle
  • Preserves accessibility via aria-expanded and aria-controls attributes using useId()

Test plan

  • Open settings page with collapsible sidebar sections (pages with subpages)
  • Click a parent item to expand — verify sub-items appear
  • Click the same parent item to collapse — verify sub-items hide
  • Repeat expand/collapse multiple times — verify toggle works consistently
  • Verify chevron icon rotates correctly on expand/collapse
  • Navigate to a subpage and verify its parent auto-expands
  • Test nested sub-items (depth > 1) expand/collapse if applicable

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Accessibility Improvements

    • Per-submenu ARIA controls added for clearer screen reader and keyboard interaction.
  • New Features

    • Nested submenus now render consistently with distinct toggle controls and IDs.
  • Bug Fixes

    • Toggle now only opens submenus for items with children; item actions fire reliably on every click.
  • UI Improvements

    • Button styling, disabled states and tooltips applied directly for more consistent visuals.

…nteraction

Replace Base-UI Collapsible with state-driven conditional rendering in
LayoutMenu. The Collapsible.Trigger render prop chain through
SidebarMenuButton's Tooltip wrapper broke event handling and animation
state tracking after the first expand cycle.

- Use handleClick + setOpen for direct toggle control
- Conditionally render SidebarMenuSub with {open && ...}
- Preserve accessibility via aria-expanded and aria-controls with useId
- Remove @base-ui/react/collapsible dependency from layout-menu

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

Replaced Collapsible primitives with local React open state and conditional rendering. Added per-item useId() for aria-controls/aria-expanded. Adjusted click handling to always fire item callbacks but only toggle open when children exist. Removed Collapsible.Panel and related wrappers.

Changes

Cohort / File(s) Summary
Menu Collapse/Expand Refactor
src/components/wordpress/layout-menu.tsx
Replaced @base-ui/react/collapsible usage with per-item open state and conditional {open && <SidebarMenuSub.../>} rendering. Added useId()-generated submenuId wired to aria-controls/aria-expanded. Unified click handler: item.onClick/onItemClick always invoked; open toggles only if item has children. Moved class/disabled/active/tooltip props onto SidebarMenuButton/SidebarMenuSubButton; added placeholder render for nested parent sub-items and removed Collapsible.Panel render-prop plumbing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐇 I hopped through code with gentle paws,

Swapped collapsibles for smaller laws,
IDs for doors and clicks that rhyme,
Menus fold tidy, one at a time,
A ribboned carrot for accessible claws 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: fixing a sidebar collapse/expand toggle bug that occurred after the first interaction.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sidebar-collapse-toggle

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@src/components/wordpress/layout-menu.tsx`:
- Around line 446-459: The parent menu items with children currently drop
item.href and item.onClick making expandable rows disclosure-only; update the
JSX where SidebarMenuButton is rendered (the instance that sets tooltip,
isActive, onClick={handleClick}, aria-expanded={open},
aria-controls={submenuId}) to preserve and prefer item.href and item.onClick
when present (e.g., pass href={item.href} and onClick={item.onClick ??
handleClick} or call item.onClick inside handleClick), so parent
LayoutMenuItemData with children still performs its navigation/callback; apply
the same change to the other identical block referenced (the block around lines
582-594) to keep item-level actions consistent.
- Around line 582-594: The nested disabled parent is still interactive because
the rendered button in SidebarMenuSubButton ignores item.disabled; update the
render/button and click handling so disabled items cannot be focused or
expanded: pass disabled and aria-disabled attributes into the rendered <button>
(e.g. disabled={item.disabled} aria-disabled={item.disabled}), set
tabIndex={item.disabled ? -1 : undefined}, and add a guard in the onClick
handler (handleClick) to immediately return when item.disabled is true; also
ensure any classNames (menuItemClassName/activeItemClassName/item.className)
reflect a disabled style when item.disabled so pointer interactions are blocked
visually/semantically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b3b5fc1d-ab8d-417c-91da-90eaf7ada25f

📥 Commits

Reviewing files that changed from the base of the PR and between 91b60e4 and ff48f7a.

📒 Files selected for processing (1)
  • src/components/wordpress/layout-menu.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/wordpress/layout-menu.tsx (1)

549-576: ⚠️ Potential issue | 🟡 Minor

Leaf sub-items don't respect item.disabled.

The button element lacks disabled={item.disabled}, so disabled leaf sub-items remain interactive. This is inconsistent with the nested parent sub-item (line 585) which does set disabled.

🛡️ Proposed fix
         <SidebarMenuSubButton
           render={
             comp === "a"
               ? <a href={item.href} />
-              : <button type="button" />
+              : <button type="button" disabled={item.disabled} />
           }
           isActive={isActive}
-          onClick={handleClick}
+          onClick={item.disabled ? undefined : handleClick}
           className={cn(
             menuItemClassName,
             isActive && activeItemClassName,
             item.className,
           )}
           data-active={isActive || undefined}
+          aria-disabled={item.disabled || undefined}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/wordpress/layout-menu.tsx` around lines 549 - 576, Leaf
sub-items aren't using item.disabled so they remain interactive; update the leaf
branch in the render where SidebarMenuSubButton is returned to pass the disabled
prop from item (i.e., add disabled={item.disabled}) and ensure click handling
respects it (e.g., avoid invoking handleClick when item.disabled is true) for
the element created via comp (anchor or button) inside SidebarMenuSubButton;
reference SidebarMenuSubButton, SidebarMenuSubItem, comp, item.disabled, and
handleClick to locate and fix the code.
♻️ Duplicate comments (1)
src/components/wordpress/layout-menu.tsx (1)

581-598: ⚠️ Potential issue | 🟡 Minor

Disabled handling is incomplete for nested parent sub-items.

The button case correctly sets disabled={item.disabled}, but two issues remain:

  1. Anchor case: When item.href exists, anchors don't support disabled natively—clicks will still navigate.
  2. onClick handler: handleClick fires regardless of item.disabled, so even if the button is disabled, callbacks may still execute on some browsers/assistive tech.
🛡️ Proposed fix
       <SidebarMenuSubButton
         render={
           item.href
-            ? <a href={item.href} />
+            ? <a href={item.disabled ? undefined : item.href} aria-disabled={item.disabled || undefined} />
             : <button type="button" disabled={item.disabled} />
         }
         isActive={isActive}
-        onClick={handleClick}
+        onClick={item.disabled ? undefined : handleClick}
         className={cn(
           menuItemClassName,
           isActive && activeItemClassName,
           item.className,
+          item.disabled && "pointer-events-none opacity-50",
         )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/wordpress/layout-menu.tsx` around lines 581 - 598, The nested
parent sub-item handling must prevent interaction when item.disabled is true:
update the render passed to SidebarMenuSubButton so the anchor branch also
respects disabled by adding aria-disabled="true", tabIndex={-1} (or equivalent),
role="button", and an onClick that calls event.preventDefault() when
item.disabled; additionally wrap or guard the handleClick passed to onClick so
it returns early if item.disabled is true (or pass a noop) to ensure clicks
never run the callback for disabled items; reference SidebarMenuSubButton,
item.href, item.disabled, and handleClick when making the changes.
🧹 Nitpick comments (1)
src/components/wordpress/layout-menu.tsx (1)

533-539: Consider guarding callbacks against disabled state.

For consistency and defense-in-depth, consider adding an early return when item.disabled is true, even though the button/anchor should prevent the click.

♻️ Optional defensive fix
   const handleClick = useCallback(() => {
+    if (item.disabled) return;
     if (hasChildren) {
       setOpen((o) => !o);
     }
     item.onClick?.();
     onItemClick?.(item);
-  }, [hasChildren, item, onItemClick]);
+  }, [hasChildren, item, item.disabled, onItemClick]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/wordpress/layout-menu.tsx` around lines 533 - 539, Add a
defensive early return in the handleClick callback to no-op when item.disabled
is true: at the top of handleClick (the useCallback that toggles setOpen for
hasChildren and calls item.onClick and onItemClick), check if (item.disabled)
return; and update the useCallback dependency list to include item.disabled (or
item.disabled via item) so the closure stays current; this prevents any
accidental activation when the item is disabled while preserving the existing
behavior for hasChildren, setOpen, item.onClick, and onItemClick.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/wordpress/layout-menu.tsx`:
- Around line 549-576: Leaf sub-items aren't using item.disabled so they remain
interactive; update the leaf branch in the render where SidebarMenuSubButton is
returned to pass the disabled prop from item (i.e., add
disabled={item.disabled}) and ensure click handling respects it (e.g., avoid
invoking handleClick when item.disabled is true) for the element created via
comp (anchor or button) inside SidebarMenuSubButton; reference
SidebarMenuSubButton, SidebarMenuSubItem, comp, item.disabled, and handleClick
to locate and fix the code.

---

Duplicate comments:
In `@src/components/wordpress/layout-menu.tsx`:
- Around line 581-598: The nested parent sub-item handling must prevent
interaction when item.disabled is true: update the render passed to
SidebarMenuSubButton so the anchor branch also respects disabled by adding
aria-disabled="true", tabIndex={-1} (or equivalent), role="button", and an
onClick that calls event.preventDefault() when item.disabled; additionally wrap
or guard the handleClick passed to onClick so it returns early if item.disabled
is true (or pass a noop) to ensure clicks never run the callback for disabled
items; reference SidebarMenuSubButton, item.href, item.disabled, and handleClick
when making the changes.

---

Nitpick comments:
In `@src/components/wordpress/layout-menu.tsx`:
- Around line 533-539: Add a defensive early return in the handleClick callback
to no-op when item.disabled is true: at the top of handleClick (the useCallback
that toggles setOpen for hasChildren and calls item.onClick and onItemClick),
check if (item.disabled) return; and update the useCallback dependency list to
include item.disabled (or item.disabled via item) so the closure stays current;
this prevents any accidental activation when the item is disabled while
preserving the existing behavior for hasChildren, setOpen, item.onClick, and
onItemClick.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e768b5dd-ee48-4f31-a4ea-c126f1e94059

📥 Commits

Reviewing files that changed from the base of the PR and between ff48f7a and 5d1e910.

📒 Files selected for processing (1)
  • src/components/wordpress/layout-menu.tsx

@kzamanbd kzamanbd merged commit a6a2897 into main Apr 1, 2026
1 check passed
@kzamanbd kzamanbd deleted the fix/sidebar-collapse-toggle branch April 1, 2026 08:41
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.

1 participant