CORE-2708: Compose render-callback classNames in react-aria-components wrappers - #136
Conversation
CI status:
|
There was a problem hiding this comment.
Pull request overview
Fixes react-aria-components wrapper components so caller-supplied render-callback className props are preserved (instead of being silently dropped by classnames), matching established RAC patterns in this codebase.
Changes:
- Use
composeRenderPropsto compose base wrapper classes with both string and render-callbackclassNameforms inNavBarMenuItem,NavBarPopover,NavBarButton, andTreeCheckbox. - Widen
NavBarButtonPropsby removing theclassName?: stringre-declaration so the RAC callback form is supported by the public API. - Add targeted tests verifying both callback and string
classNamecomposition; document the fix in the changelog.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/Tree/TreeCheckbox.tsx | Compose base + caller className via composeRenderProps so callback classNames reach the DOM. |
| src/components/Tree/TreeCheckbox.spec.tsx | Add DOM assertions covering callback and string className composition (including disabled modifier). |
| src/components/NavBarMenuButtons.tsx | Compose base + caller className for NavBarMenuItem and NavBarPopover using composeRenderProps. |
| src/components/NavBarMenuButtons.spec.tsx | Add tests verifying callback and string className composition for NavBarMenuItem and NavBarPopover. |
| src/components/NavBarButton.tsx | Stop narrowing className in props and compose base + caller className with composeRenderProps. |
| src/components/NavBarButton.spec.tsx | Add tests verifying callback and string className composition for NavBarButton. |
| CHANGELOG.md | Document the fixed render-callback className behavior and the NavBarButtonProps type widening. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Pin bumped — and it found the actual problem: the Jira credentials are 401ingPushed The It fails in 4 seconds, before any issue search runs. v0.1.16 added a That also explains the things I couldn't reconcile in my earlier comment: why #127 fails the same check while correctly linked, why #134/#135 merged red, and why the candidate JQL returns CORE-2708 fine when I run it under my own credentials. v0.1.15 had no What needs to happen next — not something I can do
So they resolve from org-level Someone with org admin needs to rotate the Atlassian API token behind the org What I'd suggest for this PRThe pin bump is worth keeping regardless of when the token gets rotated — it converts a silent, misleading failure into a one-line diagnosis, and it's the version this repo's own upstream workflow uses. But the check will stay red until the secret is fixed, and that is unrelated to the component changes under review. Happy to go either way: merge with the check red (as #134/#135 were), or hold until the token is rotated. Your call — and if you'd rather the pin bump not ride along on a component-library bugfix now that we know the fix is elsewhere, say so and I'll pull it into its own PR. Still open from beforeThe |
7dd3d9b to
a15d901
Compare
|
Correcting my earlier write-up on this thread, and closing out the CI thread. The I was wrong to conclude the org's Atlassian token was expired or revoked and needed an admin to rotate it. Nothing was wrong with the token. My reasoning was that both inputs rendered as The Ready for review. One note on the local suite: |
NavBarMenuItem, NavBarPopover, NavBarButton, and TreeCheckbox built their class list with classnames(base, className). react-aria-components types className as string | ((renderProps) => string), and classnames ignores function arguments, so a caller's render-callback className was silently dropped -- no type error, no runtime warning. Use RAC's composeRenderProps to normalise both forms, matching the fix applied to Mask, OverlayMask, and OverlayWrapper in #127. NavBarButtonProps re-declared className?: string, which intersected the callback form away. Removed that re-declaration so the wrapper exposes the same className API as ButtonProps; string values are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
a15d901 to
ac4bf1d
Compare
Jira: CORE-2708
Problem
Several
ui-componentswrappers aroundreact-aria-components(RAC) added their base CSS class withclassNames('base', className). Any RAC props type extendingRenderProps<T>typesclassNameasstring | ((values) => string)— the callback form is how consumers style against component state (isOpen,isFocused,isSelected, …).classnamesignores function arguments, so the callback was silently discarded: the caller's classes never reached the DOM, and neither TypeScript nor the runtime reported anything.Changes
Use RAC's
composeRenderPropsto normalise both forms, following the pattern already applied toMask,OverlayMask, andOverlayWrapperin #127.NavBarMenuItemsrc/components/NavBarMenuButtons.tsxNavBarPopoversrc/components/NavBarMenuButtons.tsxTreeCheckboxsrc/components/Tree/TreeCheckbox.tsxNavBarButtonsrc/components/NavBarButton.tsxTreeCheckboxkeeps building its class list into a local, but the local is now the composed callback, so the conditionalcheckbox-label--disabledclass stays in the composed output.NavBarButtondecisionThe ticket asked for a recorded decision here. Widened, rather than left narrowed.
NavBarButtonPropsre-declaredclassName?: string, which intersected withButtonPropsand narrowed the callback form away. Nothing was silently dropped (the type prevented it), but the wrapper removed a supported RAC API from the public surface for no stated reason. Removing the re-declaration lets the type inherit the callback form fromButtonProps, and the implementation now composes it. This only widens what is accepted — existing stringclassNamecallers are unaffected. A comment on the type records why it is not re-declared, so the narrowing does not creep back in.Testing
Each fixed component has a test that passes a callback
classNameand asserts the composed result, plus a companion test asserting the string form still composes. Confirmed each new callback test fails before the fix (verified by reverting the four source files and re-running), so they are load-bearing rather than decorative.npm test— 240 passed, 116 snapshots passed. No snapshot churn: the emitted class list for the string form is unchanged.npm run lint— clean.npx tsc --noEmit— clean.Out of scope, but confirmed real (flagged per the ticket)
stylehas the same callback-vs-value duality inStyleRenderProps, and these wrappers spread it into a plain object literal ({...style}). I checked whether that matters: it is the same bug, not a theoretical one.<NavBarPopover style={() => ({color: 'red'})} />type-checks cleanly (npx tsc --noEmit) — spreading a function into an object literal is legal TS and contributes no properties.TreeCheckbox: with astylecallback,element.style.coloris"".So
styleis less protected than I assumed, not more — there is no type error to catch it. It needs the samecomposeRenderPropstreatment, but it is a distinct change (the wrappers merge their CSS-variable objects intostyle, so composing means merging inside a callback rather than at the top of the component). Left out of this PR to keep it scoped to the ticket's acceptance criteria — happy to file the follow-up, or fold it in here if reviewers would rather it ship together.🤖 Generated with Claude Code
CI:
Jiracheck — resolved (correcting an earlier wrong diagnosis)This check now passes. The cause was that
.github/workflows/checks.ymlreferencedsecrets.JiraEmail/secrets.JiraToken, but the org secrets are namedJIRA_EMAIL/JIRA_TOKEN. The mismatched names resolved to empty strings, so the action sent an empty Basic auth header and Jira returned 401. @RoyEJohnson fixed the casing in a15d901.I had concluded from the 401 that the org's Atlassian API token was expired or revoked and needed an admin to rotate it. That was wrong — nothing was wrong with the token, and no rotation is needed. I reasoned that because both inputs rendered as
***in the job log they must be set and non-empty; that inference doesn't hold, since GitHub masks the input display either way.The
v0.1.16pin bump I pushed during review is no longer on this branch, and it isn't needed —v0.1.15passes now that the secret names are right. It was still useful in the moment: v0.1.16 adds aGET /rest/api/3/myselfpre-flight that surfaced the 401 in 4 seconds, where v0.1.15 has noresponse.okcheck anywhere and funnels any auth failure into the misleadingno matching issues found. That's worth fixing upstream on its own merits, but it's not this PR's business.🤖 Generated with Claude Code
CI:
jira-linked-actionpin bumped (added in review)Per review,
.github/workflows/checks.ymlnow pinsopenstax/jira-linked-action@v0.1.16(wasv0.1.15).This did not make the
Jiracheck pass, but it diagnosed it. v0.1.16 adds aGET /rest/api/3/myselfcredential pre-flight, and the check now fails in 4s with:So the check was never failing over linking, project keys, or dev-status indexing — the org-level
JiraEmail/JiraTokensecrets are being rejected. v0.1.15 had noresponse.okcheck, so it passed the 401 body into the search logic and reported it asno matching issues found. Fixing it requires rotating the Atlassian API token behind the org secret, which needs org admin. See the comment below for the full write-up.build(both workflows) andDeployare green.