Skip to content

feat (sidebar): add variants, more section menu and separator for section header in collapsed state#720

Open
paanSinghCoder wants to merge 10 commits intomainfrom
feat/sidebar-design-improvements
Open

feat (sidebar): add variants, more section menu and separator for section header in collapsed state#720
paanSinghCoder wants to merge 10 commits intomainfrom
feat/sidebar-design-improvements

Conversation

@paanSinghCoder
Copy link
Copy Markdown
Contributor

@paanSinghCoder paanSinghCoder commented Mar 25, 2026

Description

  • feat: add separator for the collapsed state.
  • feat: add variants plain, floating and inset.
  • feat: add more section menu.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactor (no functional changes, no bug fixes just code improvements)
  • Chore (changes to the build process or auxiliary tools and libraries such as documentation generation)
  • Style (changes that do not affect the meaning of the code (white-space, formatting, etc))
  • Test (adding missing tests or correcting existing tests)
  • Improvement (Improvements to existing code)
  • Other (please specify)

How Has This Been Tested?

[Describe the tests that you ran to verify your changes]

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (.mdx files)
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Screenshots (if appropriate):

[Add screenshots here]

Related Issues

[Link any related issues here using #issue-number]

@paanSinghCoder paanSinghCoder self-assigned this Mar 25, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 25, 2026

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

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Apr 9, 2026 6:49am

@paanSinghCoder paanSinghCoder changed the title feat: add separator for section title in collapsed state feat (sidebar): add separator and More section and menu Mar 25, 2026
@paanSinghCoder paanSinghCoder changed the title feat (sidebar): add separator and More section and menu feat (sidebar): add separator for section header in collapsed state Mar 26, 2026
@paanSinghCoder paanSinghCoder removed the request for review from rohanchkrabrty March 26, 2026 06:45
@paanSinghCoder paanSinghCoder marked this pull request as draft March 26, 2026 06:45
@paanSinghCoder paanSinghCoder changed the title feat (sidebar): add separator for section header in collapsed state feat (sidebar): add separator for section header in collapsed state and add More section Mar 26, 2026
@paanSinghCoder paanSinghCoder changed the title feat (sidebar): add separator for section header in collapsed state and add More section feat (sidebar): add separator for section header in collapsed state Mar 26, 2026
@paanSinghCoder paanSinghCoder marked this pull request as ready for review March 26, 2026 08:28
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Sidebar.More and SidebarLeadingVisual components; introduces a variant prop on SidebarRoot (plain | floating | inset) and emits data-variant on the root; adds accordion behavior to Sidebar.Group with new props (accordion, value, trailingIcon); implements new CSS for variants, collapsed headers, group panel transitions, and “more” menu styling; integrates Sidebar.More into the Sidebar API and docs/demos (new demos: variantDemo, accordionGroupDemo, updated moreDemo); updates examples to use Radix icons; and adds tests for variant, accordion groups, and Sidebar.More.

Sequence Diagram(s)

sequenceDiagram
    participant Page as Page / Consumer
    participant Sidebar as SidebarRoot (Context)
    participant More as Sidebar.More (Trigger)
    participant Tooltip as Tooltip (optional)
    participant Menu as Menu (Dropdown)
    participant Item as Sidebar.Item

    Page->>Sidebar: render (props: open, position, variant, hideCollapsedItemTooltip)
    Sidebar->>Sidebar: provide context { isCollapsed, position, hideCollapsedItemTooltip }
    Page->>More: render More with children items
    More->>Sidebar: read context (isCollapsed, position)
    More->>More: build trigger (leadingIcon or default DotsHorizontalIcon)
    alt isCollapsed && tooltip allowed
        More->>Tooltip: wrap trigger in Tooltip
        Tooltip->>Menu: on trigger open -> show Menu positioned by Sidebar.position
    else
        More->>Menu: render Trigger directly -> open Menu
    end
    Menu->>Item: render Menu.Item for each child (clone props, render SidebarLeadingVisual, label)
    Item-->>Page: user selects item -> navigation/action
Loading

Possibly related issues

Suggested reviewers

  • rohanchkrabrty
  • rsbh
🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the three main features added: sidebar variants, a more section menu, and a separator for collapsed section headers.
Description check ✅ Passed The description relates to the changeset, listing the three main features being added, though it lacks detail about implementation and testing.

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


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.

@paanSinghCoder paanSinghCoder marked this pull request as draft March 26, 2026 10:04
@paanSinghCoder paanSinghCoder removed the request for review from rohanchkrabrty March 26, 2026 10:05
@paanSinghCoder paanSinghCoder marked this pull request as ready for review March 26, 2026 11:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
apps/www/src/app/examples/page.tsx (1)

139-146: Consider adding a label for accessibility.

Sidebar.More without a label prop (only leadingIcon) may lack a descriptive accessible name in collapsed state when tooltips are shown.

♿ Optional: Add label for better accessibility
-              <Sidebar.More leadingIcon={<DotsHorizontalIcon />}>
+              <Sidebar.More label="More options" leadingIcon={<DotsHorizontalIcon />}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/app/examples/page.tsx` around lines 139 - 146, Sidebar.More
currently only provides a leadingIcon (<DotsHorizontalIcon />) and lacks an
accessible label; add a descriptive label prop to Sidebar.More (e.g.,
label="More") so screen readers and collapsed/tooltip states have a clear name.
Update the Sidebar.More component usage in apps/www/src/app/examples/page.tsx to
pass a concise label string alongside leadingIcon, and ensure any tooltip logic
uses that label if applicable.
packages/raystack/components/sidebar/__tests__/sidebar.test.tsx (1)

290-322: Good test coverage for Sidebar.More.

The tests cover both expanded and collapsed states with proper accessibility assertions. Consider using screen.getByRole('button', { name: 'More items' }) for a more robust trigger selection instead of .closest('button').

♻️ Optional: Use role-based query for trigger
-      const trigger = screen.getByText('More items').closest('button');
-      expect(trigger).toBeInTheDocument();
-      if (!trigger) return;
-      fireEvent.click(trigger);
+      const trigger = screen.getByRole('button', { name: /More items/i });
+      fireEvent.click(trigger);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx` around lines
290 - 322, Replace the fragile DOM traversal used to find the More trigger in
the 'Sidebar More' test: instead of locating the label text and calling
.closest('button') (see the test that uses screen.getByText('More
items').closest('button')), use a role-based query like
screen.getByRole('button', { name: 'More items' }) to reliably select the
trigger; remove the subsequent if (!trigger) return guard and assert directly
that the returned element exists before firing events.
packages/raystack/components/sidebar/sidebar.module.css (1)

259-267: Minor formatting inconsistency: extra blank line inside media query.

There's an extra blank line at line 260 inside the @media (prefers-reduced-motion: reduce) block that differs from the typical formatting in this file.

🔧 Suggested fix
 `@media` (prefers-reduced-motion: reduce) {
-
   .root,
   .nav-item,
   .nav-text,
   .resizeHandle {
     transition: none;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/sidebar/sidebar.module.css` around lines 259 -
267, Remove the extra blank line inside the `@media` (prefers-reduced-motion:
reduce) block so its formatting matches the rest of the file; specifically,
collapse the blank line between the media query declaration and the selector
list that includes .root, .nav-item, .nav-text, and .resizeHandle so the block
reads as a contiguous rule set without an empty line.
packages/raystack/components/sidebar/sidebar-more.tsx (1)

90-143: Consider the item.key fallback behavior.

Using item.key ?? index at line 138 is functional, but React's key prop on a component is only accessible via item.key when iterating over children. If consumers don't provide explicit keys to SidebarItem children, all items will use index-based keys, which can cause issues with list reordering or dynamic items.

This is acceptable for typical sidebar menus with static items, but consider documenting that consumers should provide unique keys for dynamic item lists.

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

In `@packages/raystack/components/sidebar/sidebar-more.tsx` around lines 90 - 143,
The current key fallback uses item.key ?? index which can cause problems for
reordering; update the key selection in the items.map -> Menu.Item by preferring
an explicit unique identifier before falling back to index (e.g., check
item.key, then item.props.id or another stable identifier on the child) and add
a short developer-facing note/documentation near SidebarMore (or SidebarItem)
explaining that consumers should provide explicit keys/ids for dynamic lists to
avoid reconciliation issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/raystack/components/sidebar/sidebar-more.tsx`:
- Around line 51-72: The button element inside triggerContent currently has
role="listitem", which conflicts with its function as a menu trigger; remove the
explicit role attribute from the button in sidebar-more.tsx so native button
semantics apply, or if this is intended to be presented as a menu item use
role="menuitem" (and add aria-haspopup/aria-expanded as appropriate) instead;
update the JSX where triggerContent is defined (the <button> that renders
SidebarLeadingVisual and label) accordingly.

---

Nitpick comments:
In `@apps/www/src/app/examples/page.tsx`:
- Around line 139-146: Sidebar.More currently only provides a leadingIcon
(<DotsHorizontalIcon />) and lacks an accessible label; add a descriptive label
prop to Sidebar.More (e.g., label="More") so screen readers and
collapsed/tooltip states have a clear name. Update the Sidebar.More component
usage in apps/www/src/app/examples/page.tsx to pass a concise label string
alongside leadingIcon, and ensure any tooltip logic uses that label if
applicable.

In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx`:
- Around line 290-322: Replace the fragile DOM traversal used to find the More
trigger in the 'Sidebar More' test: instead of locating the label text and
calling .closest('button') (see the test that uses screen.getByText('More
items').closest('button')), use a role-based query like
screen.getByRole('button', { name: 'More items' }) to reliably select the
trigger; remove the subsequent if (!trigger) return guard and assert directly
that the returned element exists before firing events.

In `@packages/raystack/components/sidebar/sidebar-more.tsx`:
- Around line 90-143: The current key fallback uses item.key ?? index which can
cause problems for reordering; update the key selection in the items.map ->
Menu.Item by preferring an explicit unique identifier before falling back to
index (e.g., check item.key, then item.props.id or another stable identifier on
the child) and add a short developer-facing note/documentation near SidebarMore
(or SidebarItem) explaining that consumers should provide explicit keys/ids for
dynamic lists to avoid reconciliation issues.

In `@packages/raystack/components/sidebar/sidebar.module.css`:
- Around line 259-267: Remove the extra blank line inside the `@media`
(prefers-reduced-motion: reduce) block so its formatting matches the rest of the
file; specifically, collapse the blank line between the media query declaration
and the selector list that includes .root, .nav-item, .nav-text, and
.resizeHandle so the block reads as a contiguous rule set without an empty line.
🪄 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: 399fa010-1e7a-4d64-a652-02526abcd1cd

📥 Commits

Reviewing files that changed from the base of the PR and between 68c3edf and d52afdd.

📒 Files selected for processing (11)
  • apps/www/src/app/examples/page.tsx
  • 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/sidebar-item.tsx
  • packages/raystack/components/sidebar/sidebar-leading-visual.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

@paanSinghCoder paanSinghCoder changed the title feat (sidebar): add separator for section header in collapsed state feat (sidebar): add variants and separator for section header in collapsed state Mar 27, 2026
@paanSinghCoder paanSinghCoder changed the title feat (sidebar): add variants and separator for section header in collapsed state feat (sidebar): add variants, more section menu and separator for section header in collapsed state Mar 27, 2026
* feat: add accordion and trailingIcon

* feat: update Sidebar variant to 'floating' and add accordion group demo with collapsible sections
Comment on lines +23 to +26
align='center'
gap={3}
className={cx(styles['nav-leading-icon'], className)}
aria-hidden='true'
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.

can we do a commonProps object and then reuse

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Created a local HOC.

className,
label,
value,
accordion = false,
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.

Let's not call it accordion, we should stick with collapsible

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment on lines +90 to +92
<span className={cx(styles['nav-leading-icon'], classNames?.icon)}>
{leadingIcon}
</span>
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.

I think we can reuse the LeadingVisual component here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +108 to +114
<Flex
direction='column'
className={cx(styles['nav-group-items'], classNames?.items)}
role='list'
>
{children}
</Flex>
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.

same here too, the icon container styles can be reusued. the hover styles can be passed as classname

Comment on lines +141 to +147
{leadingIcon && (
<span
className={cx(styles['nav-leading-icon'], classNames?.icon)}
>
{leadingIcon}
</span>
)}
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.

same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +158 to +167
{trailingIcon ? (
<span
className={cx(
styles['nav-group-trailing-icon'],
classNames?.trailingIcon
)}
>
{trailingIcon}
</span>
) : null}
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.

same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +90 to +135
{items.map((item, index) => {
const {
leadingIcon: itemLeadingIcon,
active,
disabled,
as = <a />,
classNames: itemClassNames,
children: itemLabel,
...itemProps
} = item.props;

const render = cloneElement(
as,
{
className: cx(
styles['nav-item'],
styles['more-menu-item'],
itemClassNames?.root,
classNames?.menuItem
),
'data-active': active,
'data-disabled': disabled,
'aria-current': active ? 'page' : undefined,
'aria-disabled': disabled,
...itemProps
},
<>
<SidebarLeadingVisual
leadingIcon={itemLeadingIcon}
fallbackText={
typeof itemLabel === 'string' ? itemLabel : undefined
}
className={itemClassNames?.leadingIcon}
/>
<span
className={cx(
styles['nav-text'],
styles['more-menu-item-text'],
itemClassNames?.text
)}
>
{itemLabel}
</span>
</>
);

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.

Let's skip this clone element thing and just accept children here.

Let's create a sidebarmorecontext here. And then use that to conditionally render the Sidebar.Item

const insideSidebarMore = useSidebarMoreContext();

if(insideSidebarMore) return <Menu.Item disabled={disabled}/>
else return <Sidebar.Item/>

Also I think the Items inside Menu should have the menu item styles instead of the sidebar item styles. Check with design once

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

DOne

Comment on lines +42 to +46
const items = Children.toArray(children).filter(
isValidElement
) as ReactElement<SidebarItemProps>[];

if (items.length === 0) return null;
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.

This is also not needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed

'aria-hidden': true
} as const;

const content = cloneElement(
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.

I think this can be done using useRender instead of clone now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Using useRender

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
packages/raystack/components/sidebar/sidebar-misc.tsx (2)

158-167: Trailing icon clicks will propagate to accordion trigger.

The trailingIcon is rendered inside the accordion header but outside the trigger. However, if the intent is for trailing icon interactions to not toggle the accordion, consider wrapping with onClick={(e) => e.stopPropagation()} on the span, especially if consumers expect action buttons in this slot.

🛡️ Proposed fix to prevent event propagation
 {trailingIcon ? (
   <span
     className={cx(
       styles['nav-group-trailing-icon'],
       classNames?.trailingIcon
     )}
+    onClick={(e) => e.stopPropagation()}
   >
     {trailingIcon}
   </span>
 ) : null}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/sidebar/sidebar-misc.tsx` around lines 158 -
167, The trailing icon span currently rendered for trailingIcon (className using
styles['nav-group-trailing-icon'] and classNames?.trailingIcon) can allow clicks
to bubble and toggle the accordion; update the span that renders {trailingIcon}
to attach an onClick handler that calls e.stopPropagation() (and optionally
e.preventDefault() if needed) so interactions with the trailingIcon do not
propagate to the accordion trigger.

125-130: Accordion remounts on collapse/expand, resetting user's open/closed state.

Using key={isCollapsed ? 'collapsed' : 'expanded'} forces a full remount of the accordion when the sidebar toggles. This means any user-controlled open/closed state is lost. While this achieves the "force open when collapsed" behavior, it also resets state when expanding back.

If preserving user's accordion state across sidebar expand/collapse is desired, consider controlling the accordion's value prop based on isCollapsed rather than using a key-based remount.

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

In `@packages/raystack/components/sidebar/sidebar-misc.tsx` around lines 125 -
130, The Accordion is being force-remounted via key={isCollapsed ? 'collapsed' :
'expanded'}, which resets user open/closed state; remove that key and make
AccordionPrimitive.Root controlled instead: replace defaultValue with a
controlled value prop (e.g., accordionValue) and add onValueChange to persist
user changes; compute the value such that when isCollapsed is true you override
to the collapsed-forced value ([groupValue]) but when false you restore the
stored accordionValue so the user's previous open/closed state is preserved
(initialize accordionValue from groupValue and update it in onValueChange).
packages/raystack/components/sidebar/__tests__/sidebar.test.tsx (2)

400-435: Test relies on implicit event behavior that may be fragile.

This test wraps trailingIcon with a <button> that has its own onClick. The test passes because clicking the inner button doesn't propagate to the accordion trigger. However, from Context Snippet 2 (sidebar-misc.tsx:158-167), the trailingIcon is rendered as a plain <span> without explicit stopPropagation(). If a consumer passes a non-interactive element (e.g., just an icon), clicking it would toggle the accordion.

Consider adding a test case with a non-button trailingIcon to document expected behavior, or add stopPropagation in the implementation if accordion should never toggle on trailing icon clicks.

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

In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx` around lines
400 - 435, The test currently uses a clickable <button> as the trailingIcon
which prevents propagation, masking the real behavior; update the Sidebar.Group
tests to include a second test that renders a non-interactive trailingIcon
(e.g., a plain <span data-testid="group-trailing-icon">+</span>) and assert
whether clicking it toggles the accordion to document the intended behavior, or
alternatively modify the Sidebar implementation (where trailingIcon is rendered
— e.g., the component/function that renders the trailingIcon in Sidebar.Group /
sidebar-misc) to call event.stopPropagation() on clicks of the trailing icon so
accordion toggling is prevented; pick one approach and add the corresponding
test or implementation change and ensure tests cover both interactive (button)
and non-interactive trailingIcon cases.

458-469: Test description may be misleading - role='listitem' is always applied.

Per Context Snippet 1 (sidebar-more.tsx:51-72), role='listitem' is applied unconditionally (line 59), not just when collapsed. Only aria-label is conditional on isCollapsed. The test name "sets aria-label for collapsed More trigger" is accurate for the aria-label assertion, but querying by role='listitem' doesn't specifically exercise collapsed-only behavior.

Consider clarifying the test name or splitting into two assertions: one for the always-present role and one for the conditional aria-label.

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

In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx` around lines
458 - 469, The test currently queries the always-present role on Sidebar.More
(role='listitem') but claims to verify collapsed-only behavior; update tests
around Sidebar.More/isCollapsed: either rename the test to "sets aria-label when
collapsed" and keep the aria-label assertion for BasicSidebar open={false}, or
split into two tests — one asserting that role='listitem' is always present
(render BasicSidebar open={true} and check getByRole('listitem')), and a
separate test that renders BasicSidebar open={false} and asserts the conditional
aria-label exists on the trigger; reference Sidebar.More and the
isCollapsed-dependent aria-label logic in sidebar-more.tsx when making the
change.
packages/raystack/components/sidebar/sidebar.module.css (1)

274-286: Trailing icon hidden by default, appears on hover.

The opacity: 0 with hover reveal is a nice subtle UX pattern. Be aware this may reduce discoverability for new users and is not keyboard-accessible (no :focus-within rule). Consider adding :focus-within if the trailing icon contains interactive elements.

♻️ Suggested addition for focus accessibility
 .nav-group-header:hover .nav-group-trailing-icon {
   opacity: 1;
 }
+
+.nav-group-header:focus-within .nav-group-trailing-icon {
+  opacity: 1;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/sidebar/sidebar.module.css` around lines 274 -
286, The trailing icon (.nav-group-trailing-icon) is hidden by default via
opacity:0 and only shown on .nav-group-header:hover, which is not
keyboard-accessible; update the stylesheet to also reveal the icon when the
header receives keyboard focus by adding a selector for
.nav-group-header:focus-within .nav-group-trailing-icon (and ensure any
interactive element inside .nav-group-trailing-icon can receive focus), so
keyboard users see the trailing icon the same way hover users do.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/raystack/components/sidebar/__tests__/sidebar.test.tsx`:
- Around line 400-435: The test currently uses a clickable <button> as the
trailingIcon which prevents propagation, masking the real behavior; update the
Sidebar.Group tests to include a second test that renders a non-interactive
trailingIcon (e.g., a plain <span data-testid="group-trailing-icon">+</span>)
and assert whether clicking it toggles the accordion to document the intended
behavior, or alternatively modify the Sidebar implementation (where trailingIcon
is rendered — e.g., the component/function that renders the trailingIcon in
Sidebar.Group / sidebar-misc) to call event.stopPropagation() on clicks of the
trailing icon so accordion toggling is prevented; pick one approach and add the
corresponding test or implementation change and ensure tests cover both
interactive (button) and non-interactive trailingIcon cases.
- Around line 458-469: The test currently queries the always-present role on
Sidebar.More (role='listitem') but claims to verify collapsed-only behavior;
update tests around Sidebar.More/isCollapsed: either rename the test to "sets
aria-label when collapsed" and keep the aria-label assertion for BasicSidebar
open={false}, or split into two tests — one asserting that role='listitem' is
always present (render BasicSidebar open={true} and check
getByRole('listitem')), and a separate test that renders BasicSidebar
open={false} and asserts the conditional aria-label exists on the trigger;
reference Sidebar.More and the isCollapsed-dependent aria-label logic in
sidebar-more.tsx when making the change.

In `@packages/raystack/components/sidebar/sidebar-misc.tsx`:
- Around line 158-167: The trailing icon span currently rendered for
trailingIcon (className using styles['nav-group-trailing-icon'] and
classNames?.trailingIcon) can allow clicks to bubble and toggle the accordion;
update the span that renders {trailingIcon} to attach an onClick handler that
calls e.stopPropagation() (and optionally e.preventDefault() if needed) so
interactions with the trailingIcon do not propagate to the accordion trigger.
- Around line 125-130: The Accordion is being force-remounted via
key={isCollapsed ? 'collapsed' : 'expanded'}, which resets user open/closed
state; remove that key and make AccordionPrimitive.Root controlled instead:
replace defaultValue with a controlled value prop (e.g., accordionValue) and add
onValueChange to persist user changes; compute the value such that when
isCollapsed is true you override to the collapsed-forced value ([groupValue])
but when false you restore the stored accordionValue so the user's previous
open/closed state is preserved (initialize accordionValue from groupValue and
update it in onValueChange).

In `@packages/raystack/components/sidebar/sidebar.module.css`:
- Around line 274-286: The trailing icon (.nav-group-trailing-icon) is hidden by
default via opacity:0 and only shown on .nav-group-header:hover, which is not
keyboard-accessible; update the stylesheet to also reveal the icon when the
header receives keyboard focus by adding a selector for
.nav-group-header:focus-within .nav-group-trailing-icon (and ensure any
interactive element inside .nav-group-trailing-icon can receive focus), so
keyboard users see the trailing icon the same way hover users do.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc3bba8f-0ceb-4adf-b99a-bc5159b0673d

📥 Commits

Reviewing files that changed from the base of the PR and between 2ab2808 and 3f08348.

📒 Files selected for processing (6)
  • apps/www/src/app/examples/page.tsx
  • 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-misc.tsx
  • packages/raystack/components/sidebar/sidebar.module.css
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/www/src/content/docs/components/sidebar/demo.ts
  • apps/www/src/app/examples/page.tsx
  • apps/www/src/content/docs/components/sidebar/index.mdx

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.

2 participants