CORE-2005: Migrate navigation components (SidebarNav, ButtonNav, Pagination) to plain CSS - #140
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
9c7cc55 to
6dd5198
Compare
6dd5198 to
104f005
Compare
RoyEJohnson
left a comment
There was a problem hiding this comment.
Shall we stack this PR on ui-components/pull/143? Seems like all the open PRs should, actually. What do you think?
104f005 to
3973291
Compare
|
@RoyEJohnson Yes — done, this one is now based on Why it wasn't optionalRebasing onto #143 and running its own checker: ...and the same for So the three components now follow your convention: The nice part: it kills a caveat I'd written into the descriptionThe PR previously said the sidebar backdrop z-indexes had to be literals. The reason was real — z-index: calc(var(--ox-z-index-navbar) + 1); /* was: 11 */
z-index: calc(var(--ox-z-index-sidebar) - 1); /* was: 19 */That's two hardcoded numbers that were silently coupled to Two judgement calls to check
Three tests deleted. The On "all the open PRs should" — mostly, but not #138 and #139I checked the file overlaps, and for those two the dependency runs the other way:
#143 had to regenerate the ProfileMenu and HelpMenu snapshots because the token sweep changed values inside them. #138 and #139 delete those snapshot files outright. So if they land first, #143 doesn't need to touch either file and that part of its diff disappears; stacking them on #143 instead means #143 does churn that's immediately thrown away. There's a practical reason to prefer that order too: the flaky ProfileMenu snapshot #138 removes is currently failing in a full-suite run on #143's branch — I confirmed it fails identically there with a cold cache, without any of my commits. Landing #138 early clears it. For #137 the overlap is real code, so it and #143 should be sequenced — I'd suggest #143 rebasing onto #137, since #137 already has #141 stacked on it and is much the smaller diff, but you know the merge order you want better than I do. Verification
|
3973291 to
6194fd1
Compare
This comment was marked as resolved.
This comment was marked as resolved.
6194fd1 to
f1f4ad9
Compare
This comment was marked as resolved.
This comment was marked as resolved.
Migrate SidebarNav, ButtonNav and Pagination from styled-components to plain CSS, following the pattern established by CORE-2004 (#130). - Add SidebarNav/SidebarNav.css, ButtonNav/ButtonNav.css and Pagination/Pagination.css; delete the two styles.ts files - Move collapsedWidth/expandedWidth to SidebarNav/constants.ts so SidebarNavStyles keeps exporting them - Bind theme values as CSS custom properties via CSSPropertiesWithVariables; the sidebar backdrop rules target siblings of the nav, which cannot inherit them, so those z-indexes stay literal with a comment pointing back at theme.ts - Point the SidebarNav story at .sidebar-nav-toggle instead of interpolating SidebarNavStyles.ToggleButton as a selector - Hoist usePaginationRanges above Pagination's early return; the styled() wrapper had been hiding a rules-of-hooks violation that would throw when totalPages changed to 1 Public React APIs are unchanged and the rendered DOM is identical apart from class names, so consumers wrapping these with styled() still work. Implements: CORE-2005 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…r hook order - NavHeader and NavFooter are public through SidebarNavStyles and were styled-components before this migration, so they forwarded refs for free. Restore that with React.forwardRef. - Add a SidebarNav test that attaches a ref to each exported sub-component. - Add a Pagination test that rerenders from totalPages=10 to 1 and back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
All nine snapshot assertions in the three specs this PR touches are replaced with explicit assertions, and the three .snap files are deleted. - Pagination: the four DOM snapshots encoded the range algorithm's output in 372 lines. They are now page-sequence assertions like ['1', '...', '4', '5*', '6', '...', '10'] read off the rendered list. The assertion-less 'expands middle range to the left' test now asserts the ranges from calculatePaginationRanges, where that branch is actually observable. - ButtonNav: the two snapshots were mostly SVG path data. Replaced with assertions on grouping, arrow order, disabled state and -- new coverage -- that the arrow click handlers fire. - SidebarNav: dropped the two toggle snapshots, whose behaviour was already asserted, after making the collapsing/expanding classes, toggle state and accessible names explicit. The 50-line BodyPortal inline snapshot is now assertions about where the nav is mounted. Theme values bound as CSS custom properties are asserted against the theme rather than against hardcoded hex, so they no longer need updating if the palette moves. Fixes the misleading comment on the hook-order test, which described a crash that the old ordering did not actually produce. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Per review: the exported Pagination now only decides whether there is anything to paginate, and the page list moves into PaginationPages, which it renders when there is. The hooks live in PaginationPages and are unconditional by construction, so the ordering invariant is structural rather than held up by a comment warning the next person not to add a hook above the early return. usePaginationRanges no longer runs at all for a single page (verified: 0 calls at totalPages=1, 1 call at totalPages=10). The guard widens from `totalPages === 0 || totalPages === 1` to `totalPages > 1`, so a negative count now renders nothing instead of a nav built from a negative range. Covered by a test. Props are extracted to a PaginationProps interface now that two components share them; the public docblock stays on the exported component. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stacked on #143, so these three components use the :root token file rather than binding theme values inline per component -- the same sweep #143 applied to everything already migrated. - SidebarNav/ButtonNav/Pagination CSS now reads var(--component-hook, var(--ox-token)), and the inline CSSPropertiesWithVariables objects are gone. - The sidebar backdrop z-indexes stop being literals. That caveat only existed because component-scoped custom properties inherit down the tree and the backdrop rules target siblings of the nav; :root tokens reach siblings fine, so these are now calc(var(--ox-z-index-navbar) + 1) and calc(var(--ox-z-index-sidebar) - 1). - #959595 on the toggle button border is genuinely off-palette, carried over verbatim from the styled-components original, so it goes in KNOWN_OFF_PALETTE with a reason rather than being snapped to neutralMedium, which would be a visual change rather than a refactor. The three "binds theme values as custom properties" tests are dropped: there is no inline binding left to assert, and tokens.spec.ts now checks the theme wiring centrally. Replaced where a public contract survives -- that consumers can still override through the documented --sidebar-nav-* / --pagination-* hooks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f1f4ad9 to
2e5825a
Compare
|
@RoyEJohnson Resolved — but the conflicts weren't in this PR, so flagging where they actually were. #140 merge-tested clean against its base the whole time (GitHub had it What was wrong with #143Its branch still carried a stale copy of #137's commit ( Rebased #143's own ten commits onto the current #137 head, dropping the stale duplicate. Two resolutions, both the same underlying call:
The
Then this PRRebased onto the new #143 head, Stack now reads One nice side effectThe full suite is now 386 passing, 40 suites, zero failures. The Recovery points if you want any of this unwound: #143 was |
Jira: CORE-2005 (parent: CORE-1777 — move ui-components off styled-components)
Follows the pattern established by CORE-2004 / #130.
What changed
SidebarNav/styles.ts(navStyles+ 4 styled sub-components)SidebarNav/SidebarNav.cssButtonNav/styles.ts(4 styled components)ButtonNav/ButtonNav.cssstyled(...)wrappers inindex.tsxPagination/Pagination.cssTheme values come from the global
:roottokens added in #143 — each component's CSS readsvar(--component-hook, var(--ox-token)), so the--sidebar-nav-*/--pagination-*/--button-nav-*override hooks stay public while the defaults track the theme.classNameshandles conditional classes.collapsedWidth/expandedWidthmoved to a newSidebarNav/constants.tssoSidebarNavStyleskeeps exporting them — assignments readsSidebarNavStyles.collapsedWidthin three places.Notes for review
Sidebar backdrop z-indexes are literals, not variables.Resolved by stacking on #143. The rules.sidebar-nav.mobile ~ main::beforeand~ [data-backdrop-target]::beforestyle siblings of the nav, and component-scoped custom properties inherit down the tree rather than across siblings — so while theme values were bound inline on the nav, those two z-indexes had to be written as the literals11and19. The:roottokens reach siblings fine, so they are nowcalc(var(--ox-z-index-navbar) + 1)andcalc(var(--ox-z-index-sidebar) - 1).margin-left: 5.6remon.mobile + navis unchanged; it is a layout value, not a theme one.--collapsed-width/--expanded-widthstay declared in the CSS file rather than bound inline, because@media (max-width: 15em)reassigns--expanded-widthto100vwand an inline binding would always beat the media query.One hook-ordering fix.
PaginationcalledusePaginationRangesafter an earlyreturn nullfortotalPages <= 1. Thestyled()wrapper meant eslint didn't see it as a component, soreact-hooks/rules-of-hooksnever fired; as a plain component it does.Per review, this is now fixed structurally rather than by hoisting the hook: the exported
Paginationis a gatekeeper that only decides whether there is anything to paginate, and the page list lives inPaginationPages, which it renders when there is. The hooks sit inPaginationPagesand are unconditional by construction, so nothing relies on a comment telling the next person not to add a hook above an early return.usePaginationRangesalso no longer runs at all for a single page (verified: 0 calls attotalPages=1, 1 attotalPages=10).The guard widened from
totalPages === 0 || totalPages === 1tototalPages > 1, so a negative count renders nothing rather than a nav built from a negative range. There is a test for it.This is a real bug — React throws "rendered fewer hooks than expected" whenCorrection: it was not. Writing the regression test Copilot asked for showed the old ordering passes. React's check istotalPageschanges to 1.currentHook !== null && currentHook.next !== null, so it only fires once at least one hook has run on the update;usePaginationRangeswas the component's only hook, so the short render consumed zero hooks and the mismatch went undetected. The old ordering was a latent landmine — the next hook added above that early return would have turned it into a genuine crash — not a live one.Pagination.csskeys offli > arather than.pagination-link. Eight downstream repos wrapUI.LinkForPagein their ownPagecomponent and assessments' Review screen renders it directly; keying off the element keeps them styled even if a consumer supplies its own anchor.LinkForPagestill carries apagination-linkclass for anyone who wants to target it.Backward compatibility
Public React APIs and rendered DOM are unchanged apart from class names — the regenerated snapshots differ only in
class/styleattributes, structure is identical.styled(SidebarNav)andstyled(BodyPortalSidebarNav)still work (assignments uses both) becauseclassNameis still forwarded.What no longer works is interpolating a sub-component as a selector, e.g.
${SidebarNavStyles.ToggleButton} { ... }. An org-wide code search finds that only in this repo's ownSidebarNav.stories.tsx, updated here to use.sidebar-nav-toggle.Worth a smoke test in assignments against a pre-release tag before release: consumer
styled()overrides now compete with a plain.sidebar-navclass at similar specificity rather than with a single generated class, so injection order matters more than it did. This is the same trade-off the earlier phases of CORE-1777 accepted.Verification
npm test— 386 passing, 40 suites, no failures. TheProfileMenureact-aria snapshot failure noted here through earlier rounds is gone: CORE-2715: replace flaky ProfileMenu snapshot with targeted assertions #138 landed onmainand replaced that snapshot with targeted assertions, so it is no longer in the stack.npm run lint,npm run typecheck— clean.npm run build— succeeds; confirmed all three new.cssfiles are rsynced intodist/esmanddist/cjs.main, then pixel-diffed them:mainDate.now()textDate.now()textSeven of nine are pixel-for-pixel identical; the two that differ do so only in the bounding box of the timestamp the
UsingBodyPortalstory renders itself. Specifically confirmed by eye: the mobile backdrop still dimsmainwhile leaving the expanded sidebar above it (the sibling-selector rule with the hardcoded z-indexes), and the disabled ButtonNav arrow still renders at 0.5 opacity via:disabledrather than the old prop interpolation.🤖 Generated with Claude Code