Skip to content

feat: add Sidebar.Trigger, collapseMode, and peekOnHover; fix a11y roles and label truncation - #867

Open
ravisuhag wants to merge 8 commits into
mainfrom
fix/sidebar-a11y-text-polish
Open

feat: add Sidebar.Trigger, collapseMode, and peekOnHover; fix a11y roles and label truncation#867
ravisuhag wants to merge 8 commits into
mainfrom
fix/sidebar-a11y-text-polish

Conversation

@ravisuhag

@ravisuhag ravisuhag commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

Started as an a11y/truncation audit of the Sidebar component and grew into a broader pass: it also adds a Sidebar.Trigger subcomponent and two new collapse behaviors (collapseMode, peekOnHover) designed collaboratively to keep the prop surface small and every combination meaningful.

New: Sidebar.Trigger

  • A button that toggles the sidebar open/closed, works whether the Sidebar is controlled or uncontrolled — drop it into Sidebar.Header (or elsewhere) without wiring up open/onOpenChange yourself.
  • Disabled automatically when collapsible={false}.
  • useSidebar and SidebarContextValue are now exported from the package for custom header/footer/item content that needs to read collapse state.

New: collapseMode — what "collapsed" looks like

  • collapseMode="icon" (default): unchanged — shrinks to an icon rail, pushes content.
  • collapseMode="hidden": collapses to a thin edge strip and reveals as a floating panel with a backdrop when opened. Closes on backdrop click or Escape.
  • Deliberately a single prop rather than separate "collapsed shape" and "reveal mode" props — every value produces a sensible, unambiguous result instead of a matrix with invalid combinations.
  • Known limitation (documented): the floating panel uses position: fixed, so it won't line up correctly under an ancestor with its own transform/filter/contain.
  • Sidebar.Trigger nested inside Sidebar.Header/Main/Footer gets hidden along with everything else when collapseMode="hidden" is closed — docs call this out and show the working pattern (Trigger as a direct child of <Sidebar>, or a separate toggle outside it entirely).

New: peekOnHover

  • Hovering a collapsed sidebar temporarily reveals it as the same floating panel, without touching the real open state — similar to VS Code's auto-hide sidebar. Reverts on mouse leave; clicking the resize handle or a Sidebar.Trigger pins it open for real.
  • No effect when collapsible={false} or while already open (documented).
  • No keyboard equivalent in this pass — mitigated by existing per-item tooltips on focus, which already surface full labels to keyboard/screen reader users.

Bug fix found during review

  • During a peek, the sidebar's width expanded but item/group labels stayed invisible — several CSS rules hid content keyed on data-closed alone, which stays true throughout a peek (it tracks open, not isPeeking). Added floating-state overrides for every affected rule (.nav-text, .nav-group-header, .nav-group-label, .nav-group-chevron, [data-collapse-hidden]), placed after the hide rules so they win the cascade tie-break. Verified live in a browser via computed styles before/after the fix.

Accessibility (original scope)

  • Sidebar.Main is now role="list" (was role="group"); items render role="listitem", which needs a list parent.
  • Groups render as role="listitem" (was an unlabeled <section>, which mapped to a region landmark). Together with the inner role="list" items container, groups now form valid nested lists (ul > li > ul).
  • Removed role="banner" from Sidebar.Header — banner is a page-level landmark and must not nest inside navigation.
  • Added: Escape closes the collapseMode="hidden" floating panel; documented that the floating panel doesn't trap focus or set aria-modal.

Text truncation (original scope)

  • Group labels and more-menu labels now truncate with an ellipsis, matching item labels, with min-width: 0 so ellipsis reliably engages inside flex rows.
  • Expanded items whose label is clipped now show the full text in a tooltip on hover/focus (previously only collapsed items got this).

Cleanup

  • Consolidated identical typography declarations across .nav-text, .nav-group-label, .more-menu-item-text into one shared rule.
  • white-space: nowraptext-wrap: nowrap, matching button/breadcrumb/filter-chip.
  • Removed the deprecated tooltipMessage prop (superseded by collapseTooltip).

Testing

  • 56/56 sidebar unit tests pass (new coverage for Sidebar.Trigger, collapseMode, peekOnHover); 2152/2152 across the full package.
  • Verified in the docs site in a real browser: roles are correct in the DOM, truncation and tooltips behave as described, the collapseMode="hidden" floating panel opens/closes via backdrop click and Escape, and peekOnHover expands with fully visible labels and reverts on mouse-leave (confirmed via computed styles, not just visual inspection).

- Sidebar.Main now uses role=list and groups render as role=listitem,
  forming valid nested lists (previously items were orphaned listitems
  and groups were accidental region landmarks)
- Remove invalid role=banner from Sidebar.Header (banner is a page-level
  landmark and must not be nested inside the navigation landmark)
- Truncate group labels and more-menu labels with ellipsis, matching
  item labels; add min-width: 0 so ellipsis engages inside flex rows
- Show a tooltip with the full label when an expanded item's text is
  clipped (previously tooltips only appeared when collapsed)
- Consolidate the label typography shared by item, group, and more-menu
  text into one rule; switch white-space: nowrap to text-wrap: nowrap
  for consistency with other components
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Jul 28, 2026 11:03am

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 36d0bd02-5437-4e2f-ae83-95ca5d33df72

📥 Commits

Reviewing files that changed from the base of the PR and between 2706d78 and 09f9184.

📒 Files selected for processing (14)
  • apps/www/src/content/docs/components/sidebar/demo.ts
  • apps/www/src/content/docs/components/sidebar/index.mdx
  • packages/raystack/components/sidebar/__tests__/sidebar.test.tsx
  • packages/raystack/components/sidebar/sidebar-footer.tsx
  • packages/raystack/components/sidebar/sidebar-group.tsx
  • packages/raystack/components/sidebar/sidebar-header.tsx
  • packages/raystack/components/sidebar/sidebar-item.tsx
  • packages/raystack/components/sidebar/sidebar-misc.tsx
  • packages/raystack/components/sidebar/sidebar-more-context.tsx
  • packages/raystack/components/sidebar/sidebar-more.tsx
  • packages/raystack/components/sidebar/sidebar-root.tsx
  • packages/raystack/components/sidebar/sidebar.module.css
  • packages/raystack/components/sidebar/sidebar.tsx
  • packages/raystack/figma/sidebar-group.figma.ts
💤 Files with no reviewable changes (1)
  • packages/raystack/components/sidebar/sidebar-misc.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/raystack/components/sidebar/sidebar.tsx
  • packages/raystack/components/sidebar/sidebar-item.tsx
  • packages/raystack/components/sidebar/sidebar-root.tsx
  • apps/www/src/content/docs/components/sidebar/demo.ts
  • packages/raystack/components/sidebar/tests/sidebar.test.tsx
  • apps/www/src/content/docs/components/sidebar/index.mdx

📝 Walkthrough

Walkthrough

Sidebar collapse now supports icon and hidden modes, hover peeking, popup-aware state, and configurable collapse tooltips. Sidebar.Trigger and useSidebar expose integrated state control. Navigation semantics use nested list roles, while labels support truncation and conditional clipped-label tooltips. Documentation, demos, API declarations, styling, and tests cover the updated behavior.

Sequence Diagram(s)

sequenceDiagram
  participant SidebarTrigger
  participant SidebarRoot
  participant SidebarItem
  participant Tooltip
  SidebarTrigger->>SidebarRoot: Toggle open state
  SidebarRoot->>SidebarItem: Provide collapsed and peeking state
  SidebarItem->>Tooltip: Show tooltip when label is clipped
  Tooltip->>SidebarItem: Report tooltip state
Loading

Suggested reviewers: rsbh, rohanchkrabrty

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main Sidebar API additions and accessibility/truncation fixes.
Description check ✅ Passed The description is clearly related to the Sidebar changes and matches the implemented features and fixes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

Adds Sidebar.Trigger for wiring a toggle button without managing state,
plus two new root props: collapseMode ("icon" | "hidden") to control
whether collapsing shrinks to an icon rail or hides completely behind a
floating panel with a backdrop, and peekOnHover to preview a collapsed
sidebar on hover without changing the real open state.

Also fixes a bug where peeking expanded the sidebar's width but left
item and group labels invisible (CSS collapse-hiding rules keyed only
on data-closed, which stays true during a peek), and updates the docs
with the new props, corrected behavior claims, and accessibility notes.
@ravisuhag ravisuhag changed the title fix: sidebar a11y roles and label truncation polish feat: add Sidebar.Trigger, collapseMode, and peekOnHover; fix a11y roles and label truncation Jul 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/raystack/components/sidebar/sidebar-misc.tsx (1)

150-152: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Collapsible group header misses the trailingIcon modifier class.

The non-collapsible branch (line 104-106) conditionally adds styles['nav-group-header-with-trailing'] when trailingIcon is set, but the collapsible branch's AccordionPrimitive.Header doesn't, even though trailingIcon is rendered here too (line 171-174). Collapsible groups with a trailing icon will miss the intended header layout adjustment.

🐛 Proposed fix
           <AccordionPrimitive.Header
-            className={cx(styles['nav-group-header'], classNames?.header)}
+            className={cx(
+              styles['nav-group-header'],
+              trailingIcon && styles['nav-group-header-with-trailing'],
+              classNames?.header
+            )}
           >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/sidebar/sidebar-misc.tsx` around lines 150 -
152, Update the collapsible branch’s AccordionPrimitive.Header className to
conditionally include styles['nav-group-header-with-trailing'] when trailingIcon
is set, matching the non-collapsible header behavior and preserving the existing
nav-group-header and classNames?.header classes.
🧹 Nitpick comments (2)
packages/raystack/components/sidebar/__tests__/sidebar.test.tsx (1)

174-180: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test doesn't verify what its title claims.

hides header/main/footer content when collapsed and hidden only asserts data-collapse-mode/data-floating attributes on nav; it never checks that Sidebar.Header/Main/Footer content is actually hidden (e.g. not.toBeVisible() or absence from the accessibility tree). A regression that stops the CSS from hiding content wouldn't be caught by this test.

♻️ Suggested strengthening
     it('hides header/main/footer content when collapsed and hidden', () => {
       render(<BasicSidebar open={false} collapseMode='hidden' />);

       const nav = screen.getByRole('navigation');
       expect(nav).toHaveAttribute('data-collapse-mode', 'hidden');
       expect(nav).not.toHaveAttribute('data-floating');
+      expect(screen.getByText(HEADER_TEXT)).not.toBeVisible();
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx` around lines
174 - 180, Strengthen the test named “hides header/main/footer content when
collapsed and hidden” by asserting that the rendered Sidebar.Header,
Sidebar.Main, and Sidebar.Footer content is hidden or absent from the
accessibility tree when open is false and collapseMode is hidden. Keep the
existing navigation attribute assertions unchanged.
packages/raystack/components/sidebar/sidebar-misc.tsx (1)

114-116: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Clipped-label tooltip pattern not extended to group and "more" trigger labels.

SidebarItem now tracks a ref and compares scrollWidth/clientWidth to conditionally show a tooltip only when its expanded label is actually clipped. Both nav-group-label and the SidebarMore trigger label share the same new truncation/ellipsis styling but never get an equivalent tooltip, so a clipped group or "More"-button label offers no way to see the full text — a gap relative to the PR's stated "tooltips for clipped labels when expanded" behavior.

  • packages/raystack/components/sidebar/sidebar-misc.tsx#L114-L116: wrap the group's label <span> with the same ref + scrollWidth > clientWidth controlled Tooltip pattern used in SidebarItem (apply to both the non-collapsible and collapsible label spans).
  • packages/raystack/components/sidebar/sidebar-more.tsx#L54-L54: apply the same clip-detection Tooltip wrapping to the trigger's label <span> when the sidebar is expanded (i.e. not isCollapsed).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/sidebar/sidebar-misc.tsx` around lines 114 -
116, The group label spans in
packages/raystack/components/sidebar/sidebar-misc.tsx#L114-L116 and the trigger
label span in packages/raystack/components/sidebar/sidebar-more.tsx#L54 lack
clipped-label tooltips. Extend the SidebarItem ref plus scrollWidth/clientWidth
detection and Tooltip pattern to both non-collapsible and collapsible group
labels, and apply it to SidebarMore’s label only when isCollapsed is false;
preserve the existing label styling and collapsed behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/www/src/content/docs/components/sidebar/index.mdx`:
- Line 53: Revise the sidebar navigation description to avoid beginning three
consecutive sentences with “Use,” while preserving the explanations of
collapsible, hideCollapsedItemTooltip, collapseMode, and peekOnHover.

In `@packages/raystack/components/sidebar/sidebar-root.tsx`:
- Around line 96-138: Update the floating-state logic around showFloating so an
active peek is only treated as floating while the sidebar remains closed. When
open becomes true during peeking, ensure showFloating becomes false immediately
so the normal open layout is rendered, while preserving floating behavior for
hidden open sidebars and closed peeking sidebars.

In `@packages/raystack/components/sidebar/sidebar.module.css`:
- Around line 55-61: Update the floating-reveal selectors for .header, .main,
and .footer, including the related collapse-hiding rules around the referenced
sections, so their specificity matches or exceeds the closed-hiding selectors in
the hidden collapse mode. Ensure data-floating overrides opacity, visibility,
and pointer-events while peeking, without changing the normal data-closed
behavior when the sidebar is not floating.

---

Outside diff comments:
In `@packages/raystack/components/sidebar/sidebar-misc.tsx`:
- Around line 150-152: Update the collapsible branch’s AccordionPrimitive.Header
className to conditionally include styles['nav-group-header-with-trailing'] when
trailingIcon is set, matching the non-collapsible header behavior and preserving
the existing nav-group-header and classNames?.header classes.

---

Nitpick comments:
In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx`:
- Around line 174-180: Strengthen the test named “hides header/main/footer
content when collapsed and hidden” by asserting that the rendered
Sidebar.Header, Sidebar.Main, and Sidebar.Footer content is hidden or absent
from the accessibility tree when open is false and collapseMode is hidden. Keep
the existing navigation attribute assertions unchanged.

In `@packages/raystack/components/sidebar/sidebar-misc.tsx`:
- Around line 114-116: The group label spans in
packages/raystack/components/sidebar/sidebar-misc.tsx#L114-L116 and the trigger
label span in packages/raystack/components/sidebar/sidebar-more.tsx#L54 lack
clipped-label tooltips. Extend the SidebarItem ref plus scrollWidth/clientWidth
detection and Tooltip pattern to both non-collapsible and collapsible group
labels, and apply it to SidebarMore’s label only when isCollapsed is false;
preserve the existing label styling and collapsed behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 530d358a-c5c9-4425-8a97-83cf38cad7ca

📥 Commits

Reviewing files that changed from the base of the PR and between 1a93d70 and 1e782c1.

📒 Files selected for processing (13)
  • apps/www/src/content/docs/components/sidebar/demo.ts
  • apps/www/src/content/docs/components/sidebar/index.mdx
  • apps/www/src/content/docs/components/sidebar/props.ts
  • packages/raystack/components/sidebar/__tests__/sidebar.test.tsx
  • packages/raystack/components/sidebar/index.tsx
  • packages/raystack/components/sidebar/sidebar-item.tsx
  • packages/raystack/components/sidebar/sidebar-misc.tsx
  • packages/raystack/components/sidebar/sidebar-more.tsx
  • packages/raystack/components/sidebar/sidebar-root.tsx
  • packages/raystack/components/sidebar/sidebar-trigger.tsx
  • packages/raystack/components/sidebar/sidebar.module.css
  • packages/raystack/components/sidebar/sidebar.tsx
  • packages/raystack/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/raystack/components/sidebar/sidebar-item.tsx

Comment thread apps/www/src/content/docs/components/sidebar/index.mdx Outdated
Comment thread packages/raystack/components/sidebar/sidebar-root.tsx
Comment thread packages/raystack/components/sidebar/sidebar.module.css
- Keep deprecated tooltipMessage working as an alias for collapseTooltip
- Track visual open state in data-open/data-closed so a hover peek turns
  off every collapse-hiding rule, fixing the broken peek with
  collapseMode="hidden" and removing the fragile CSS override block
- Clear the peek (and its pending timer) when the sidebar opens for real,
  so pinning it open no longer leaves a stuck fixed overlay
- Hold a hover peek while a portaled popup (More menu, truncation
  tooltip) is open instead of collapsing it mid-use
- Open item tooltips away from the sidebar edge based on position
- Restore standalone rendering of Item/Group/More outside <Sidebar> via
  an internal fallback context; public useSidebar still throws
- Let hideCollapsedItemTooltip disable the clipped-label tooltip too
- Ignore already-handled Escape keydowns in the floating panel
The floating overlay concept caused both reported bugs: opening a
hidden-mode sidebar floated it over content instead of expanding like
icon mode, and a peek jumped to the viewport edge via position:fixed —
outside any container (like the docs demos), it landed away from the
cursor and flickered in a mouseenter/mouseleave loop.

- Expanding now works the same in both collapse modes: in place,
  pushing content; hidden mode only changes the collapsed look
- A peek expands in place too, marked data-peeking with a lifted
  shadow; no fixed positioning, no backdrop, no Escape handler
- Merge the item's two Tooltip branches into one always-controlled
  Tooltip, fixing a base-ui controlled/uncontrolled warning fired on
  every peek transition
- Update docs and tests to match
A peek now paints over the adjacent content: the sidebar keeps only its
collapsed footprint in the layout — a negative inline margin cancels
everything beyond the rail/strip width — so neighbors never move while
the panel slides out. Still no fixed positioning, so it stays anchored
inside any container. Margin transitions in lockstep with width.

A real open (pinning) still expands in place and pushes content.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)
packages/raystack/components/sidebar/sidebar-item.tsx (1)

120-138: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Popup-open counter can leak if the item unmounts while its tooltip is open.

onPopupOpenChange is only called from onOpenChange, with no unmount cleanup. If this SidebarItem unmounts while tooltipOpen is true (e.g. filtered out of a list, route change while hovering), sidebar-root.tsx's openPopupCountRef never gets its matching decrement and stays inflated forever, permanently preventing that sidebar's hover-peek from collapsing on mouse-leave (until a real open=true transition resets it).

🐛 Proposed fix
   return (
     <Tooltip
       open={tooltipOpen}
       onOpenChange={open => {
         const el = textRef.current;
         const next =
           open &&
           (isCollapsed || (el != null && el.scrollWidth > el.clientWidth));
         // Report to the sidebar so an open tooltip holds a hover peek.
         if (next !== tooltipOpen) onPopupOpenChange(next);
         setTooltipOpen(next);
       }}
     >
+      {useEffect(() => {
+        return () => {
+          if (tooltipOpen) onPopupOpenChange(false);
+        };
+      }, [tooltipOpen, onPopupOpenChange])}
       <Tooltip.Trigger render={content} />

(Move the useEffect out of JSX in the actual implementation — shown inline here only to anchor the fix location.) The same gap likely exists in SidebarMore's <Menu onOpenChange={open => onPopupOpenChange(open)}>, worth checking there too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/sidebar/sidebar-item.tsx` around lines 120 -
138, Add unmount cleanup for the open-popup state in SidebarItem: when
tooltipOpen is true during unmount, call onPopupOpenChange(false) so
sidebar-root.tsx receives the matching decrement. Implement this through a
useEffect cleanup outside the JSX, and apply the same cleanup pattern to
SidebarMore’s menu open state if it can unmount while open.
🧹 Nitpick comments (2)
packages/raystack/components/sidebar/sidebar.module.css (1)

68-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the repeated 240px open-width into a CSS variable.

The literal 240px now appears in 5+ places (peek width, both position calc() margin formulas, and the pre-existing collapse-hidden max-width comment). A future width change risks updating some occurrences but not others, silently breaking the peek-margin math.

♻️ Proposed refactor
+.root {
+  --sidebar-open-width: 240px;
+}
+
 .root[data-peeking] {
-  width: 240px;
+  width: var(--sidebar-open-width);
   z-index: var(--rs-z-index-portal);
   box-shadow: var(--rs-shadow-lifted);
 }
 
 .root[data-peeking][data-position="left"] {
-  margin-right: calc(var(--rs-space-12) - 240px);
+  margin-right: calc(var(--rs-space-12) - var(--sidebar-open-width));
 }

Also applies to: 75-76, 79-80, 83-84, 87-88, 106-109

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/sidebar/sidebar.module.css` around lines 68 -
69, Extract the shared 240px open-width value into a CSS custom property in the
sidebar stylesheet, then replace the repeated width literals and related
position calc() margin values—including the collapse-hidden max-width usage—with
that variable. Update the affected selectors around .root[data-peeking] and the
referenced position/collapse rules while preserving their existing layout
behavior.
packages/raystack/components/sidebar/__tests__/sidebar.test.tsx (1)

578-623: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the clipped-label mock into a shared helper.

The Object.defineProperty(text, 'scrollWidth'/'clientWidth', ...) pair is duplicated verbatim across this test and the neighboring one (and again around line 541-577). A small simulateClippedLabel(el, { scrollWidth, clientWidth }) helper would remove the repetition and make the intent clearer at each call site.

♻️ Proposed helper
+function simulateClippedLabel(el: HTMLElement, scrollWidth = 300, clientWidth = 100) {
+  Object.defineProperty(el, 'scrollWidth', { value: scrollWidth, configurable: true });
+  Object.defineProperty(el, 'clientWidth', { value: clientWidth, configurable: true });
+}

Then each test collapses to simulateClippedLabel(text);.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx` around lines
578 - 623, Extract the repeated scrollWidth/clientWidth Object.defineProperty
setup from the sidebar tooltip tests into a shared simulateClippedLabel helper,
supporting optional width values with the existing defaults. Replace the
duplicated setup in the neighboring clipped-label tests, including the cases
around the referenced test blocks, with calls to this helper while preserving
each test’s behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/raystack/components/sidebar/sidebar-root.tsx`:
- Around line 187-201: Update the sidebar markup in the aside component and its
collapse button: remove aria-expanded from the navigation landmark, and apply
aria-expanded plus aria-controls to the toggle button only when collapsible is
true. Keep aria-expanded synchronized with open, ensure aria-controls references
the sidebar region’s stable id, and leave non-collapsible sidebars without
aria-expanded.

---

Outside diff comments:
In `@packages/raystack/components/sidebar/sidebar-item.tsx`:
- Around line 120-138: Add unmount cleanup for the open-popup state in
SidebarItem: when tooltipOpen is true during unmount, call
onPopupOpenChange(false) so sidebar-root.tsx receives the matching decrement.
Implement this through a useEffect cleanup outside the JSX, and apply the same
cleanup pattern to SidebarMore’s menu open state if it can unmount while open.

---

Nitpick comments:
In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx`:
- Around line 578-623: Extract the repeated scrollWidth/clientWidth
Object.defineProperty setup from the sidebar tooltip tests into a shared
simulateClippedLabel helper, supporting optional width values with the existing
defaults. Replace the duplicated setup in the neighboring clipped-label tests,
including the cases around the referenced test blocks, with calls to this helper
while preserving each test’s behavior.

In `@packages/raystack/components/sidebar/sidebar.module.css`:
- Around line 68-69: Extract the shared 240px open-width value into a CSS custom
property in the sidebar stylesheet, then replace the repeated width literals and
related position calc() margin values—including the collapse-hidden max-width
usage—with that variable. Update the affected selectors around
.root[data-peeking] and the referenced position/collapse rules while preserving
their existing layout behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c346aacd-8e2a-41cc-b3ef-cfe6802db4ad

📥 Commits

Reviewing files that changed from the base of the PR and between 1e782c1 and 2706d78.

📒 Files selected for processing (8)
  • apps/www/src/content/docs/components/sidebar/index.mdx
  • apps/www/src/content/docs/components/sidebar/props.ts
  • packages/raystack/components/sidebar/__tests__/sidebar.test.tsx
  • packages/raystack/components/sidebar/sidebar-item.tsx
  • packages/raystack/components/sidebar/sidebar-misc.tsx
  • packages/raystack/components/sidebar/sidebar-more.tsx
  • packages/raystack/components/sidebar/sidebar-root.tsx
  • packages/raystack/components/sidebar/sidebar.module.css
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/raystack/components/sidebar/sidebar-more.tsx
  • apps/www/src/content/docs/components/sidebar/props.ts
  • packages/raystack/components/sidebar/sidebar-misc.tsx

Comment on lines +187 to +201
<aside
className={cx(styles.root, className)}
data-position={position}
data-variant={variant}
data-open={visualOpen ? '' : undefined}
data-closed={!visualOpen ? '' : undefined}
data-collapse-disabled={!collapsible ? '' : undefined}
data-collapse-mode={collapseMode}
data-peeking={isPeeking ? '' : undefined}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
aria-label='Navigation Sidebar'
aria-expanded={open}
role='navigation'
{...props}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the referenced sidebar implementation and surrounding code.
printf '--- files ---\n'
git ls-files | rg 'packages/raystack/components/sidebar/sidebar-root\.tsx|packages/raystack/components/sidebar' || true

printf '\n--- target file outline ---\n'
ast-grep outline packages/raystack/components/sidebar/sidebar-root.tsx --view expanded || true

printf '\n--- referenced lines with context ---\n'
sed -n '140,240p' packages/raystack/components/sidebar/sidebar-root.tsx

printf '\n--- all aria-expanded/usages in sidebar root ---\n'
rg -n "aria-expanded|aria-controls|resizeHandle|collapsible|open|handleOpenChange|sidebarId" packages/raystack/components/sidebar/sidebar-root.tsx packages/raystack/components/sidebar -S || true

Repository: raystack/apsara

Length of output: 244


🌐 Web query:

MDN aria-expanded applied focusable interactive control toggles visibility avoid including elements do not control expanded state

💡 Result:

The aria-expanded attribute is a WAI-ARIA state used to indicate to assistive technology (AT) whether a focusable, interactive control is currently expanded or collapsed, and consequently whether the content it controls is visible or hidden [1][2][3]. Key implementation principles include: Application to Controls: The attribute must be applied to the focusable, interactive element that triggers the visibility change, not the controlled content itself [1][4]. Common examples include buttons for menus, accordions, and disclosure widgets [1]. Communication of State: - true: Indicates the controlled content is currently visible [1][2]. - false: Indicates the controlled content is currently hidden [1][2]. - Omitted/undefined: Indicates the element does not control an expandable region or that the state is not applicable [1][5][2]. Avoid Misuse: Because the presence of aria-expanded explicitly signals that an element controls the expanded state of another object, it should not be included on elements that do not perform this function [1][6][3]. Additionally, it should only be used for dynamic in-page changes handled by JavaScript; it is inappropriate for controls that trigger a full page refresh [2][7][3]. Best Practices: - Associating Content: Use the aria-controls attribute on the controlling element to explicitly reference the ID of the controlled element, reinforcing the relationship [1][8][4]. - Dynamic Updates: JavaScript must be used to update the aria-expanded value in real-time when the state changes, as screen readers rely on this to announce status updates to users [2][7][3]. - Default Values: Ensure the attribute is set to the correct initial state (e.g., false for a collapsed accordion) to accurately reflect the UI's starting condition [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- files ---'
git ls-files | rg 'packages/raystack/components/sidebar/sidebar-root\.tsx|packages/raystack/components/sidebar' || true

printf '%s\n' ''
printf '%s\n' '--- target file outline ---'
ast-grep outline packages/raystack/components/sidebar/sidebar-root.tsx --view expanded || true

printf '%s\n' ''
printf '%s\n' '--- referenced lines with context ---'
sed -n '140,250p' packages/raystack/components/sidebar/sidebar-root.tsx

printf '%s\n' ''
printf '%s\n' '--- sidebar aria/controls/collapsible usages ---'
rg -n "aria-expanded|aria-controls|resizeHandle|collapsible|open|handleOpenChange|sidebarId|role=\"button\"|onClick=\\(\\) => open" packages/raystack/components/sidebar packages/raystack/components/sidebar/sidebar-root.tsx -S || true

Repository: raystack/apsara

Length of output: 27947


Move aria-expanded/aria-controls off the sidebar landmark.

The toggleable state belongs on the collapse button, not the <aside role="navigation">, per aria-expanded guidance; the control also needs aria-controls to identify the sidebar region it shows/hides. Apply these attributes only when collapsible is true and update them in sync with open; when collapsible is false, leave the non-collapsible <aside> without aria-expanded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/raystack/components/sidebar/sidebar-root.tsx` around lines 187 -
201, Update the sidebar markup in the aside component and its collapse button:
remove aria-expanded from the navigation landmark, and apply aria-expanded plus
aria-controls to the toggle button only when collapsible is true. Keep
aria-expanded synchronized with open, ensure aria-controls references the
sidebar region’s stable id, and leave non-collapsible sidebars without
aria-expanded.

…ding

Resets Sidebar.Header's background for the inset variant, which
previously stayed opaque and could paint over adjacent shadowed
content. Also documents the existing data-collapse-hidden opt-in on
SidebarHeader (its content is arbitrary so it can't auto-hide like nav
items) and adds a regression test for the wiring.

Fixes #840
- Derive all sidebar widths from two CSS variables and drop the dead
  220px base width and duplicated peek-margin rules
- Share base styles between nav items, More menu items, and the More
  trigger instead of copying them
- Report tooltip/menu open state via effects so unmounting mid-popup
  can no longer leave a hover peek stuck open
- Replace the div-based resize handle with a native <button>
- Split sidebar-misc.tsx into sidebar-header/footer/group files and
  dedupe the group's accordion and plain branches
- Simplify the More context to a plain boolean and remove small DOM
  noise (data-active="false", aria-disabled="false")
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.

Sidebar: header keeps opaque background in inset variant, and custom header content isn't hidden on collapse

1 participant