-
Notifications
You must be signed in to change notification settings - Fork 579
[MISC] Enhance sidebar UX to expand on hover #1768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Summary by CodeRabbit
WalkthroughAdds a pinned/unpinned sidebar with hover-to-expand and a 300ms collapse debounce, a bottom pin toggle persisted via safe localStorage helpers, CSS scroll/flex wrappers for the sider, and moves collapse toggle responsibility from PageLayout to SideNavBar (SideNavBar now requires a Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx (1)
196-211: Conditional hook calls violate React's rules of hooks.The hooks inside the
try/catchandifblocks at lines 199-201 and 208-210 are called conditionally, which violates React's hooks rules. Hooks must be called in the same order on every render—conditional calls lead to unpredictable behavior and bugs.Since this appears to be pre-existing code (only the trailing comma formatting changed), consider addressing it in a follow-up. The typical fix is to call the hook unconditionally and handle the absence of the store gracefully:
// Call unconditionally, default to a no-op selector if store unavailable const unstractSubscriptionPlan = unstractSubscriptionPlanStore?.useUnstractSubscriptionPlanStore?.( (state) => state?.unstractSubscriptionPlan ) ?? null;
🤖 Fix all issues with AI agents
In `@frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx`:
- Around line 180-186: The collapse timeout started in handleMouseLeave (stored
on collapseTimeoutRef.current) isn't cleared on unmount; add a useEffect cleanup
that clears any pending timeout with clearTimeout(collapseTimeoutRef.current)
and optionally nulls collapseTimeoutRef.current to avoid calling setCollapsed
after the component has unmounted; ensure this effect runs once (empty deps) and
also consider clearing the timeout whenever isPinned changes or when collapsing
manually so setCollapsed won't be invoked on an unmounted component.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx`:
- Around line 162-164: The JSON.parse(localStorage.getItem("sidebarPinned"))
call can throw on corrupted data; wrap the read/parse in a try-catch inside the
SideNavBar component (where isPinned and setIsPinned are declared) so that if
JSON.parse throws you fall back to false (or a safe default) and optionally
clear the bad value from localStorage; update the initialization logic for
isPinned to use the safe parsed value from the try-catch and keep setIsPinned
usage the same.
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Outdated
Show resolved
Hide resolved
Prevents calling setCollapsed after component unmounts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Outdated
Show resolved
Hide resolved
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Outdated
Show resolved
Hide resolved
- Add try-catch for localStorage parsing to handle corrupted data - Split timeout cleanup into separate useEffects for clearer unmount handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx (1)
224-239:⚠️ Potential issue | 🟠 MajorFix conditional hook calls to comply with React Rules of Hooks.
Both
useUnstractSubscriptionPlanStoreanduseSelectedProductStoreare called conditionally (lines 225 and 235). This violates React's Rules of Hooks: hooks must be called unconditionally at the top level of a component. If store availability changes between renders, the hook call order becomes inconsistent, causing bugs. Use a safe wrapper to ensure the hooks are always called.🔧 Proposed fix (safe wrapper + unconditional call)
- try { - if (unstractSubscriptionPlanStore?.useUnstractSubscriptionPlanStore) { - unstractSubscriptionPlan = - unstractSubscriptionPlanStore?.useUnstractSubscriptionPlanStore( - (state) => state?.unstractSubscriptionPlan, - ); - } - } catch (error) { - // Do nothing - } - - if (selectedProductStore?.useSelectedProductStore) { - selectedProduct = selectedProductStore.useSelectedProductStore( - (state) => state?.selectedProduct, - ); - } + const useSafeUnstractSubscriptionPlanStore = + unstractSubscriptionPlanStore?.useUnstractSubscriptionPlanStore ?? + ((selector) => selector?.({})); + const useSafeSelectedProductStore = + selectedProductStore?.useSelectedProductStore ?? ((selector) => selector?.({})); + + try { + unstractSubscriptionPlan = useSafeUnstractSubscriptionPlanStore( + (state) => state?.unstractSubscriptionPlan, + ); + } catch { + // Do nothing + } + + selectedProduct = useSafeSelectedProductStore( + (state) => state?.selectedProduct, + );
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx`:
- Around line 157-179: The getSafeLocalStorageValue and SideNavBar useEffect
assume localStorage is always available—calling localStorage.removeItem in the
catch and localStorage.setItem in the effect can throw in SSR/tests or when
storage is blocked; add guards and safe wrappers: update
getSafeLocalStorageValue to first check typeof localStorage !== "undefined" and
only call getItem/removeItem when available (avoid calling removeItem
unconditionally in the catch), create a safeSetLocalStorage helper used in the
useEffect that wraps setItem in try/catch and no-ops when storage is unavailable
or errors, and replace direct localStorage calls in SideNavBar with these guards
(referencing getSafeLocalStorageValue, safeSetLocalStorage, and the useEffect
that persists sidebarPinned).
🧹 Nitpick comments (1)
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx (1)
553-571: Prefer a semantic button for the pin control (with aria-pressed).♻️ Suggested refactor
- <Tooltip title={isPinned ? "Unpin sidebar" : "Keep expanded"}> - <div - className="sidebar-pin-container" - onClick={togglePin} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - togglePin(); - } - }} - role="button" - tabIndex={0} - > + <Tooltip title={isPinned ? "Unpin sidebar" : "Keep expanded"}> + <button + type="button" + className="sidebar-pin-container" + onClick={togglePin} + aria-pressed={isPinned} + aria-label={isPinned ? "Unpin sidebar" : "Keep sidebar expanded"} + > {isPinned ? ( <PushpinFilled className="sidebar-pin-icon pinned" /> ) : ( <PushpinOutlined className="sidebar-pin-icon" /> )} - </div> + </button> </Tooltip>
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Outdated
Show resolved
Hide resolved
- Create clearCollapseTimeout helper to eliminate duplication - Combine 3 useEffects into 1 for localStorage, pin change, and unmount - Simplify handleMouseEnter and handleMouseLeave Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx`:
- Around line 538-556: Replace the non-semantic div used for the pin control
with a native button element: in the block rendering the pin control (where
Tooltip wraps the element, and components/symbols involved include togglePin,
isPinned, PushpinFilled, PushpinOutlined, and the "sidebar-pin-container" /
"sidebar-pin-icon" classes), change the div to a <button type="button">, remove
role="button", tabIndex and the onKeyDown keyboard handling (the native button
handles Enter/Space), keep the onClick calling togglePin, preserve Tooltip,
className and conditional icon rendering, and add an explicit accessible name
via aria-label that reflects state (e.g., aria-label={isPinned ? "Unpin sidebar"
: "Keep expanded"}).
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Outdated
Show resolved
Hide resolved
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Outdated
Show resolved
Hide resolved
frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Outdated
Show resolved
Hide resolved
Menu items now have data-testid attributes (e.g., sidebar-workflows, sidebar-api-deployments) that persist regardless of collapsed state, allowing UI tests to find elements without relying on visible text. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Guard localStorage for SSR/tests with isLocalStorageAvailable check - Wrap all localStorage operations in try-catch - Replace div with native <button> for pin toggle (accessibility) - Add aria-pressed and aria-label for screen reader support - Update CSS to reset button default styles Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx`:
- Around line 194-229: The sidebar can remain collapsed when isPinned is loaded
true because useEffect only clears timeouts; update the isPinned change handling
to force expansion: inside the useEffect watching isPinned (the effect that
calls setSafeLocalStorageValue), when isPinned is true call setCollapsed(false)
(and clearCollapseTimeout via clearCollapseTimeout()) so any persisted pinned
state immediately expands; also ensure togglePin (which flips isPinned) keeps
the existing behavior of clearing timeouts and calling setCollapsed(false) when
newPinned is true. Reference functions/vars: useEffect (dependency [isPinned]),
setSafeLocalStorageValue, clearCollapseTimeout, setCollapsed, togglePin,
setIsPinned, collapseTimeoutRef.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace native button with Ant Design Button component for consistency with rest of codebase. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Call setCollapsed(false) in useEffect when isPinned is true to ensure sidebar expands on initial load with persisted pinned state - Add optional chaining to data iteration and item property access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create helpers/localStorage.js with getLocalStorageValue and setLocalStorageValue functions - Update SideNavBar.jsx to use the common utility - Update PageLayout.jsx to use the common utility with safe access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|



What
Why
How
.ant-layout-sider-childrenwrapper.sidebar-content-wrapperwithmin-height: 0for proper flex scrollingCan this PR break any existing features. If yes, please list possible items. If no, please explain why.
No. Changes are isolated to sidebar styling and behavior. Collapse functionality is preserved and improved with new pin feature.
Notes on Testing
Screenshots
Checklist
I have read and understood the Contribution Guidelines.