Repository navigation
feat: add headerSecondaryContent and hideHeaderText to ExpandableSection - #5088
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5088 +/- ##
==========================================
+ Coverage 97.74% 97.76% +0.02%
==========================================
Files 1000 1003 +3
Lines 32309 32572 +263
Branches 11948 12110 +162
==========================================
+ Hits 31579 31843 +264
+ Misses 724 684 -40
- Partials 6 45 +39 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
dab0634 to
aca6cd7
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Secondary-content overflow and inconsistent navigation handling remain unresolved, and interaction-isolation coverage needs strengthening.
3 open issues
What changed in this PR
Adds secondary header content and visually hidden titles to ExpandableSection, supporting interactive query-builder headers while preserving accessible names and descriptions.
Changes:
- Adds
headerSecondaryContentandhideHeaderText, with labelled regions for hidden titles. - Extends shared header rendering and container attribute forwarding.
- Adds accessibility tests, documentation snapshots, and a demo page.
| File | Description |
|---|---|
src/header/styles.scss |
Styles secondary content and title alignment. |
src/header/internal.tsx |
Renders secondary content outside the heading. |
src/header/interfaces.ts |
Allows h6 and div title tags. |
src/header/__tests__/header.test.tsx |
Tests the new title-tag options. |
src/expandable-section/styles.scss |
Adds secondary-content layout and hidden-title styles. |
src/expandable-section/internal.tsx |
Connects new props and region labelling. |
src/expandable-section/interfaces.ts |
Documents the new public props. |
src/expandable-section/expandable-section-header.tsx |
Renders secondary content and updates trigger semantics. |
src/expandable-section/expandable-section-container.tsx |
Forwards region attributes to section roots. |
src/expandable-section/__tests__/expandable-section.test.tsx |
Tests slots, hidden titles, and accessible descriptions. |
src/container/internal.tsx |
Adds internal native-attribute forwarding. |
src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap |
Updates generated API documentation snapshots. |
pages/expandable-section/header-secondary-content.page.tsx |
Demonstrates interactive and supporting-text headers. |
Review effort: Balanced
1f34b14 to
6b4c743
Compare
|
Rebased onto |
There was a problem hiding this comment.
🟡 Changes recommended
End-position trigger accessibility and hidden-title analytics labeling remain unresolved.
6 open issues
Use URL parameters for dev-page state and variants · New Add accessible names to unlabeled language Select controls · New Include secondary content when positioning the expansion caret · New Avoid rendering a hidden text-side expansion button · New Apply description IDs to the standalone expansion caret · New Preserve analytics labels when section titles are hidden · New
3 resolved since last review
Review effort: Balanced
6b4c743 to
2ae5d94
Compare
2ae5d94 to
a60a1b5
Compare
a60a1b5 to
afc027c
Compare
afc027c to
b538366
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Truthiness checks mishandle valid numeric zero secondary content, causing missing content or an unwrapped zero without an accessible description; consistent presence checks and a zero-count regression test are needed.
1 open issue
1 resolved since last review
Review effort: Balanced
| const [value, setValue] = useState(''); | ||
| return ( | ||
| <ExpandableSection | ||
| variant="container" |
There was a problem hiding this comment.
Can we add one more example with variant equals to container, and it has headerCounter, headerInfo and headerDescription? It would be easy to test and see how they work together.
| warnOnce(componentName, `The \`headerActions\` prop is only supported for the "container" and "default" variants.`); | ||
| } | ||
|
|
||
| if (headerSecondaryContent && !variantSupportsActions(variant) && isDevelopment) { |
There was a problem hiding this comment.
Can we write a new function in utils, variantSupportsHeaderSecondaryContent, and use it here? This will make the code easier to understand. It will also tighten support to only the two variants, instead of ['container', 'compact', 'default', 'inline'], since headerSecondaryContent is supposed to be used only in these two variants. Something like this in utils
export function variantSupportsHeaderSecondaryContent(variant: InternalVariant) {
return variantIsOneOf(variant, ['container', 'default']);
}b538366 to
adf7df8
Compare

Description
Adds two new props to
ExpandableSectionfor the query-builder / observability header pattern, where the header row needs to carry an interactive control (or supporting text) beside the title:headerSecondaryContent— content displayed next to the header text, such as an interactive control (aSelect, a searchInput) or description-style supporting text. Supported for thedefaultandcontainervariants. It renders outside the heading element, so interactive controls are not nested inside the heading (or inside the clickable expand button). When provided, it also becomes the trigger button's accessible description (aria-describedby), so a screen reader announces the header text as the name and the secondary content as the description — in both the visible and hidden header-text cases.hideHeaderText— for headers whose only visible affordance is an interactive control. When set:role="region"), named by the header text (or byheaderAriaLabelwhen provided), so the section remains navigable once its heading is gone.To render the title without heading semantics,
Header'sheadingTagOverridenow also acceptsdiv(and the previously-missingh6). This is the one public type change:HeaderProps.HeadingTagwidens fromh1–h5toh1–h6anddiv. It is additive — existing values are unchanged — but it is a change to the sharedHeaderAPI, so the diff is a tightening only in the sense of accepting more, not fewer, values.For the container variant,
InternalHeaderowns the layout of the secondary-content slot: it renders the content in the title row (outside the heading) and applies the packing styles only when the slot is used. The region role reaches the container root through a small internal__nativeAttributespassthrough onInternalContainer, because the container's base props only forwardid/className/data-*.How has this been tested?
ExpandableSectionandHeader), across thecontainer,stacked,defaultandinlinevariants:hideHeaderTextrenders no heading element and the trigger keeps the header text as its accessible name;role="region"labelled by the header text, withheaderAriaLabeltaking precedence; no region role when the text is visible;headerSecondaryContentbecomes the trigger's accessible description in both the visible and hidden cases, and noaria-describedbywhen it is absent;axepass for a hidden-text container with a control (this fails if the region has no accessible name);headingTagOverrideacceptsh6, anddivrenders the title with no heading semantics.defaultandcontainervariants at wide and narrow widths: the control sits beside single-line header text and aligns with the header actions; it wraps onto its own line under width pressure instead of being crushed; and withhideHeaderTextthe control sits where the title would be. A demo page covering all cases is included underpages/expandable-section/.Review checklist
The following items are to be evaluated by the author(s) and the reviewer(s).
Correctness
CONTRIBUTING.md.CONTRIBUTING.md.Security
checkSafeUrlfunction.Testing
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.