fix(a11y-filter-panel): allow users to choose the level of the filter panel section heading [AC-4758] - #1424
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR aims to improve filter-panel heading accessibility by allowing configurable heading levels.
Changes:
- Adds configurable heading rendering to
FilterPanelSection. - Adds coverage for rendering an
h2.
File summaries
| File | Description |
|---|---|
| src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx | Updated as part of this pull request. |
| src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.test.tsx | Updated as part of this pull request. |
Review details
Suppressed comments (1)
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx:77
- The new contract also accepts
"p", but the added test covers only the numeric heading path, leaving this paragraph branch unverified. Add an integration assertion (throughSearchAndFilteronce the prop is forwarded) thatheadingLevel="p"renders a<p>rather than a heading.
const HeadingTag = headingLevel === "p" ? "p" : (`h${headingLevel}` as const);
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
d06614f to
c5b2797
Compare
969e756 to
d9285c8
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The implementation has a type-checking error and can emit a broken aria-labelledby reference when no heading is provided.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 2
- Review effort level: Lite
d9285c8 to
7bdec4e
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The group role is still assigned aria-expanded, creating an invalid role/state combination.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 1
- Review effort level: Lite
| /** | ||
| * Element used for the section title. Supports heading levels or paragraph. | ||
| */ | ||
| headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | "p"; |
There was a problem hiding this comment.
This is a bit odd, can we use ElementType here instead? We already use it in the Notification component.
| headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | "p"; | |
| headingElement?: ElementType; |
There was a problem hiding this comment.
Agreed @edlerd , looks cleaner, but how can we make sure users only pass valid arguments? Should we declare something like :
type SearchAndFilterHeadingElement =
| "h1"
| "h2"
| "h3"
| "h4"
| "h5"
| "h6"
| "p";
There was a problem hiding this comment.
I don't think we need to validate it here. It should be ok to let the user provide any element they like.
| /** | ||
| * Element used for each section title. Supports heading levels or paragraph. | ||
| */ | ||
| headingLevel?: 1 | 2 | 3 | 4 | 5 | 6 | "p"; |
There was a problem hiding this comment.
Here as well, better use ElementType.
… panel section heading
7bdec4e to
8efa805
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The new/updated interactive “button-like” spans need correct keyboard activation semantics and the new headingElement API typing + updated tests should be made robust/type-safe before merging.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (5)
src/components/SearchAndFilter/SearchAndFilter.tsx:318
p-search-and-filter__selected-countis a focusable element withrole="button", but itsonKeyDownhandler triggers expansion for any keypress (not just Enter/Space). This can cause unexpected expansion when using the keyboard and isn’t consistent with expected button keyboard semantics.
onClick={() => setSearchBoxExpanded(true)}
onKeyDown={() => setSearchBoxExpanded(true)}
role="button"
tabIndex={0}
aria-label={`Show ${overflowSearchTermCounter} more ${
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx:160
- The overflow counter uses
role="button"but relies ononKeyPress={showAllChips}.onKeyPressis deprecated and also fires for keys that shouldn’t activate a button; for keyboard accessibility this should be handled viaonKeyDownand limited to Enter/Space (withpreventDefault()for Space).
onKeyPress={showAllChips}
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx:39
headingElementis typed asElementType, but this component unconditionally applies intrinsic-element props (dangerouslySetInnerHTML,id,className). WithElementType(without a props generic), TypeScript won’t prevent callers from passing a component that can’t accept these props, which can break thearia-labelledbylinkage at runtime. Restrict this to supported intrinsic tags ("h1"-"h6" | "p") to match the intended usage.
/**
* Element used for the section title. Supports heading levels or paragraph.
*/
headingElement?: ElementType;
};
src/components/SearchAndFilter/SearchAndFilter.tsx:46
headingElementis typed asElementType, which implies consumers can pass arbitrary components. However,FilterPanelSectionalways passes DOM-only props likedangerouslySetInnerHTML,id, andclassNameto this element, and the type currently won’t catch incompatible components. Since the prop is intended to support heading levels (andp), narrow the public API type to those intrinsic tags so callers can’t accidentally pass unsupported components.
/**
* Element used for each section title. Supports heading levels or paragraph.
*/
headingElement?: ElementType;
};
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.test.tsx:133
- Now that the overflow counter has
role="button"and an accessible name viaaria-label, these assertions/clicks can use Testing Library role queries instead ofdocument.querySelector, making the test closer to user behavior and less tied to CSS selectors.
document.querySelector(".p-filter-panel-section__counter"),
).toBeInTheDocument();
await userEvent.click(
// Use a query selector because the element's text is split up over
// multiple elements so it can't be selected by its content.
- Files reviewed: 6/6 changed files
- Comments generated: 3
- Review effort level: Lite
|
🎉 This PR is included in version 4.11.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Done
aria-labelNote that this will now fail as the
aria-expandedattribute has been removed from the enclosing divInstead, you can test it like so:
QA
Pinging @canonical/react-library-maintainers for a review.
yarn docsand verify the added prop to the FilterPanelSectionyarn testand verify the added testFixes
Fixes: #AC-4758.