feat(fuselage): Re-land the sidebar rows and group headers restyle, with aligned spacing

IV
21d ago
+288-108

Proposed changes (including videos or screenshots)

Re-lands #2193 and #2221, which were reverted by #2232, and finishes the job they started: sidebar rows, collapse group headers and accordion headers now share one spacing and interaction contract instead of three near-copies.

Re-landed from #2193 / #2221

  • SidebarCollapseGroup accepts empty again, and renders a <div role='group' aria-labelledby> instead of a <section>.
  • The collapse group bar gets its --empty and --unread modifiers back. The shared header title is c1 (regular) so that --unread promoting it to c2 (bold) actually reads as emphasis — these two only work as a pair, and restoring one without the other made the modifier a no-op.
  • SidebarGroupTitle wraps its title and chevron in __heading again, so a collapsed group's badge sits at the far end of the bar rather than next to the chevron.
  • Rows take focus through an inset box-shadow, with border-radius: medium.

Spacing

Rows and both kinds of header now start and end at the same position, using logical properties throughout so it holds in RTL.

Sidebar item (.rcx-sidebar-item) — padding 0.25rem 0.5rem, side margins 0.5rem. The --level-2 override is gone; level-2 rows use the same padding as level-1.

Collapse group bar — padding 0.25rem 0.5rem, side margins 0.5rem, top margin 0.5rem.

Accordion item bar — top margin 0.5rem, side margins and side padding 0.5rem, block padding stays 0.75rem. Border radius small → medium, matching the rows and the collapse group bar. Still sticky, still pins flush to the top of the scroll area. The chevron now follows the title, via the same __heading wrapper the collapse group uses — previously the hidden chevron reserved space ahead of the title and pushed it out of line with the rows.

Shared vs. exclusive

&-collapse-group, &-accordion-item carried a base __bar that both children fully overrode, so the shared block was declaring things nobody used while the genuinely shared values were duplicated in each child. Split by what is actually common:

  • Shared: box model (margin-block-start, margin-inline, padding-inline), border-radius, background-color, column-gap, __title, __heading.
  • Exclusive: min-height and color per header kind, padding-block, the accordion's sticky positioning and --disabled.
  • Removed as dead: the base __bar properties both children replaced, __bar--disabled on the collapse group (it has no disabled state), the accordion's min-height (its block padding alone clears it), and __title { flex: 1 1 0 } (both children set 0 1 auto).

__menu-wrapper moved into &-collapse-group — it is the only component that renders one, and sharing it compiled the reveal rule into a 12-selector cross-product, 9 of them unmatchable. It is 3 selectors now.

The accordion bar no longer uses focus-state, which was giving it a permanent 1px transparent border that had to be subtracted back out of its padding with a calc(). It now focuses through the same inset box-shadow as the collapse group bar and the rows, so the calc() is gone and the bar's box lines up with the rows instead of sitting 2px wider. Its hover surface was tint while the collapse group bar and the rows used hover; all three now resolve to $sidebar-color-surface-hover, the themeable token the rows already respected.

SidebarItem.styles.scss was re-declaring $sidebar-color-surface-hover and $sidebar-color-surface-selected character-for-character despite already @use-ing variables.scss. Dropped the shadowing copies.

Storybook

  • Navigation/Sidebar Custom takes a display arg (condensed / medium / extended) instead of one story per density.
  • Its Favorites group is collapsed, so the group badge renders. Badge placement in the header bar had no coverage at all before this.
  • GenericMediumItem, GenericExtendedItem and GenericNoAvatarItem now mirror GenericCondensedItem's i % 2 unread pattern — highlighted icon, unread title, unread timestamp, conditional badge. Only the condensed helper showed those states, so medium and extended stories rendered every row as read.
  • CondensedItems moved from SidebarItem.stories.tsx to helpers.tsx. It was a plain export in a CSF file, so Storybook picked it up as a story and rendered a byte-identical twin of Condensed.

Sidebar rows and group headers

Condensed items (hovered row, hovered kebab menu)

Sidebar rows and group headers with condensed items, hovered row and kebab menu, before and after

Medium items (hovered row, hovered kebab menu)

Sidebar with medium items, hovered row and kebab menu, before and after

Extended items (hovered row, hovered kebab menu)

Sidebar with extended items, hovered row and kebab menu, before and after

Accordion header on hover (chevron after the title)

Accordion header on hover, before and after

Issue(s)

SCC-48

Further comments

  • The collapsed sidebar (collapsed prop) is intentionally not adjusted in this PR.
  • Snapshots removed: navigation-sidebar-item--condensed-items-desktop.png (duplicate of --condensed, from the accidental CSF export) and navigation-sidebar--custom-{medium,extended}-desktop.png (the stories they belonged to are now the display arg).
  • Expect movement in the remaining Sidebar baselines: header titles are regular with unread bold, the accordion bar is 2px narrower with its hover surface changed, the collapsed group badge moves to the end of the bar, and medium/extended rows now show unread states.

🤖 Generated with Claude Code