From 21b9aad5a12e0bcbe762b4f64e8ee0fc03a609f6 Mon Sep 17 00:00:00 2001 From: vikvorona Date: Thu, 27 Apr 2023 09:48:28 +0200 Subject: [PATCH] [NO-JIRA] Improve a11y in facet lists --- .../src/main/js/api/mocks/CodingRulesMock.ts | 36 +++++++++++++------ .../main/js/api/mocks/IssuesServiceMock.ts | 5 +++ .../coding-rules/__tests__/CodingRules-it.ts | 23 +++++++++++- .../components/AvailableSinceFacet.tsx | 8 +++-- .../components/CodingRulesApp.tsx | 11 +++--- .../js/apps/coding-rules/components/Facet.tsx | 4 ++- .../coding-rules/components/ProfileFacet.tsx | 6 +++- .../CodingRulesApp-test.tsx.snap | 16 +++------ .../sidebar/DomainFacet.tsx | 4 ++- .../sidebar/ProjectOverviewFacet.tsx | 2 +- .../__snapshots__/DomainFacet-test.tsx.snap | 12 ++++--- .../js/apps/issues/__tests__/IssuesApp-it.tsx | 10 ++++++ .../issues/sidebar/CharacteristicFacet.tsx | 5 ++- .../apps/issues/sidebar/CreationDateFacet.tsx | 2 ++ .../js/apps/issues/sidebar/PeriodFilter.tsx | 2 +- .../apps/issues/sidebar/ResolutionFacet.tsx | 4 ++- .../js/apps/issues/sidebar/ScopeFacet.tsx | 4 ++- .../js/apps/issues/sidebar/SeverityFacet.tsx | 4 ++- .../js/apps/issues/sidebar/StandardFacet.tsx | 16 ++++++--- .../js/apps/issues/sidebar/StatusFacet.tsx | 4 ++- .../main/js/apps/issues/sidebar/TypeFacet.tsx | 4 ++- .../src/main/js/apps/issues/test-utils.tsx | 5 +-- .../main/js/components/facet/FacetHeader.tsx | 8 +++-- .../js/components/facet/FacetItemsList.tsx | 20 +++++++---- .../js/components/facet/ListStyleFacet.tsx | 11 ++++-- .../components/facet/__tests__/Facet-it.tsx | 9 ++--- .../ListStyleFacet-test.tsx.snap | 20 +++++++---- 27 files changed, 181 insertions(+), 74 deletions(-) diff --git a/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts b/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts index f26cbbe3525..689828eba61 100644 --- a/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts @@ -32,18 +32,19 @@ import { Rule, RuleActivation, RuleDetails, RulesUpdateRequest } from '../../typ import { NoticeType } from '../../types/users'; import { getFacet } from '../issues'; import { - bulkActivateRules, - bulkDeactivateRules, Profile, - searchQualityProfiles, SearchQualityProfilesParameters, SearchQualityProfilesResponse, + bulkActivateRules, + bulkDeactivateRules, + searchQualityProfiles, } from '../quality-profiles'; import { getRuleDetails, getRulesApp, searchRules, updateRule } from '../rules'; import { dismissNotice, getCurrentUser } from '../users'; interface FacetFilter { languages?: string; + available_since?: string; } const FACET_RULE_MAP: { [key: string]: keyof Rule } = { @@ -158,6 +159,7 @@ export default class CodingRulesMock { ], }), mockRuleDetails({ + createdAt: '2022-12-16T17:26:54+0100', key: 'rule8', type: 'VULNERABILITY', lang: 'py', @@ -188,8 +190,8 @@ export default class CodingRulesMock { this.rules = cloneDeep(this.defaultRules); } - getRuleWithoutDetails() { - return this.rules.map((r) => + getRulesWithoutDetails(rules: RuleDetails[]) { + return rules.map((r) => pick(r, [ 'isTemplate', 'key', @@ -206,12 +208,17 @@ export default class CodingRulesMock { ); } - filterFacet({ languages }: FacetFilter) { - let filteredRules = this.getRuleWithoutDetails(); + filterFacet({ languages, available_since }: FacetFilter) { + let filteredRules = this.rules; if (languages) { filteredRules = filteredRules.filter((r) => r.lang && languages.includes(r.lang)); } - return filteredRules; + if (available_since) { + filteredRules = filteredRules.filter( + (r) => r.createdAt && new Date(r.createdAt) > new Date(available_since) + ); + } + return this.getRulesWithoutDetails(filteredRules); } setIsAdmin() { @@ -314,7 +321,14 @@ export default class CodingRulesMock { return this.reply(rule); }; - handleSearchRules = ({ facets, languages, p, ps, rule_key }: SearchRulesQuery) => { + handleSearchRules = ({ + facets, + languages, + p, + ps, + available_since, + rule_key, + }: SearchRulesQuery) => { const countFacet = (facets || '').split(',').map((facet: keyof Rule) => { const facetCount = countBy( this.rules.map((r) => r[FACET_RULE_MAP[facet] || facet] as string) @@ -328,9 +342,9 @@ export default class CodingRulesMock { const currentP = p || 1; let filteredRules: Rule[] = []; if (rule_key) { - filteredRules = this.getRuleWithoutDetails().filter((r) => r.key === rule_key); + filteredRules = this.getRulesWithoutDetails(this.rules).filter((r) => r.key === rule_key); } else { - filteredRules = this.filterFacet({ languages }); + filteredRules = this.filterFacet({ languages, available_since }); } const responseRules = filteredRules.slice((currentP - 1) * currentPs, currentP * currentPs); return this.reply({ diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts index 3304e1afd01..f2fc1dbfc74 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -181,6 +181,7 @@ export default class IssuesServiceMock { issue: mockRawIssue(false, { key: 'issue11', component: 'foo:test1.js', + creationDate: '2022-01-01T09:36:01+0100', message: 'FlowIssue', characteristic: IssueCharacteristic.Clear, type: IssueType.CodeSmell, @@ -760,6 +761,10 @@ export default class IssuesServiceMock { .filter((item) => !query.rules || query.rules.split(',').includes(item.issue.rule)) .filter( (item) => !query.resolutions || query.resolutions.split(',').includes(item.issue.resolution) + ) + .filter( + (item) => + !query.inNewCodePeriod || new Date(item.issue.creationDate) > new Date('2023-01-10') ); // Splice list items according to paging using a fixed page size diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts index 9750dfc6762..487606f7560 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts @@ -19,7 +19,7 @@ */ import { fireEvent, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { byRole } from 'testing-library-selector'; +import { byPlaceholderText, byRole } from 'testing-library-selector'; import CodingRulesMock from '../../../api/mocks/CodingRulesMock'; import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks'; import { renderAppRoutes } from '../../../helpers/testReactTestingUtils'; @@ -32,8 +32,11 @@ jest.mock('../../../api/users'); jest.mock('../../../api/quality-profiles'); const ui = { + rulesList: byRole('list', { name: 'list_of_rules' }), activateInSelectOption: byRole('combobox', { name: 'coding_rules.activate_in' }), deactivateInSelectOption: byRole('combobox', { name: 'coding_rules.deactivate_in' }), + availableSinceFacet: byRole('button', { name: 'coding_rules.facet.available_since' }), + availableSinceDateField: byPlaceholderText('date'), }; let handler: CodingRulesMock; @@ -506,6 +509,24 @@ it('should not show notification for anonymous users', async () => { ).not.toBeInTheDocument(); }); +it('should filter correctly', async () => { + const user = userEvent.setup(); + renderCodingRulesApp(mockCurrentUser()); + + expect(await within(await ui.rulesList.find()).findAllByRole('listitem')).toHaveLength(8); + await user.click(await ui.availableSinceFacet.find()); + await user.click(await ui.availableSinceDateField.find()); + await userEvent.selectOptions( + await screen.findByRole('combobox', { name: 'Month:' }), + 'November' + ); + await userEvent.selectOptions(screen.getByRole('combobox', { name: 'Year:' }), '2022'); + await user.click(screen.getByRole('gridcell', { name: '1' })); + expect(ui.availableSinceDateField.get()).toHaveDisplayValue('Nov 1, 2022'); + // eslint-disable-next-line jest-dom/prefer-in-document + expect(within(ui.rulesList.get()).getAllByRole('listitem')).toHaveLength(1); +}); + function renderCodingRulesApp(currentUser?: CurrentUser, navigateTo?: string) { renderAppRoutes('coding_rules', routes, { navigateTo, diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx index 410764f83cd..110322782bd 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx @@ -34,8 +34,10 @@ interface Props { } class AvailableSinceFacet extends React.PureComponent { + property: keyof Query = 'availableSince'; + handleHeaderClick = () => { - this.props.onToggle('availableSince'); + this.props.onToggle(this.property); }; handleClear = () => { @@ -53,10 +55,12 @@ class AvailableSinceFacet extends React.PureComponent + { /> ) : ( <> -

{translate('list_of_rules')}

-
    +
      {rules.map((rule) => ( { (key) => -stats[key], (key) => renderTextName(key).toLowerCase() )); + const headerId = `facet_${property}`; return ( { property={property} > { {open && items !== undefined && ( - {items.map(this.renderItem)} + {items.map(this.renderItem)} )} {open && this.props.renderFooter !== undefined && this.props.renderFooter()} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx index 9fbac37e959..8c9c3247362 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx @@ -165,10 +165,12 @@ export default class ProfileFacet extends React.PureComponent { ); const property = 'profile'; + const headerId = `facet_${property}`; return ( { /> - {open && {profiles.map(this.renderItem)}} + {open && ( + {profiles.map(this.renderItem)} + )} ); } diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CodingRulesApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CodingRulesApp-test.tsx.snap index 78e53adfec6..9a9f3631fce 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CodingRulesApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/CodingRulesApp-test.tsx.snap @@ -263,12 +263,9 @@ exports[`should render correctly: loaded 1`] = `
      -

      - list_of_rules -

      -
        -

        - list_of_rules -

        -
          +
      diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx index d38472c3173..638040d7232 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx @@ -149,10 +149,12 @@ export default class DomainFacet extends React.PureComponent { const { domain, open } = this.props; const helperMessageKey = `component_measures.domain_facets.${domain.name}.help`; const helper = hasMessage(helperMessageKey) ? translate(helperMessageKey) : undefined; + const headerId = `facet_${domain.name}`; return ( { /> {open && ( - + {this.renderOverviewFacet()} {this.renderItemsFacet()} diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/ProjectOverviewFacet.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/ProjectOverviewFacet.tsx index 741f09af76d..bb33f4419ea 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/ProjectOverviewFacet.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/ProjectOverviewFacet.tsx @@ -33,7 +33,7 @@ export default function ProjectOverviewFacet({ value, selected, onChange }: Prop const facetName = translate('component_measures.overview', value, 'facet'); return ( - + { ).toHaveTextContent('ts674'); }); }); + + it('should show the new code issues only', async () => { + const user = userEvent.setup(); + + renderProjectIssuesApp('project/issues?id=myproject'); + + expect(await ui.issueItems.findAll()).toHaveLength(7); + await user.click(await ui.inNewCodeFilter.find()); + expect(await ui.issueItems.findAll()).toHaveLength(6); + }); }); describe('issues item', () => { diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx index eb0b153027e..94efb0f82cd 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx @@ -141,10 +141,13 @@ export default class CharacteristicFacet extends React.PureComponent { .filter(([, value]) => value === fitFor) .map(([key]) => key as IssueCharacteristic); + const headerId = `facet_${this.property}_${fitFor}`; + return ( { {this.props.open && ( <> - + {availableCharacteristics.map(this.renderItem)} - + { render() { const { resolutions, stats = {}, forceShow, fetching, open } = this.props; const values = resolutions.map((resolution) => this.getFacetItemName(resolution)); + const headerId = `facet_${this.property}`; if (values.length < 1 && !forceShow) { return null; @@ -127,6 +128,7 @@ export default class ResolutionFacet extends React.PureComponent { { {open && ( <> - + {RESOLUTIONS.map(this.renderItem)} translate('issue.scope', scope)); const property = 'scopes'; + const headerId = `facet_${property}`; if (values.length < 1 && !forceShow) { return null; } @@ -53,6 +54,7 @@ export default function ScopeFacet(props: ScopeFacetProps) { props.onChange({ scopes: [] })} onClick={() => props.onToggle('scopes')} @@ -62,7 +64,7 @@ export default function ScopeFacet(props: ScopeFacetProps) { {open && ( <> - + {SOURCE_SCOPES.map(({ scope, qualifier }) => { const active = scopes.includes(scope); const stat = stats[scope]; diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx index f587a50b359..6fe70022f66 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx @@ -95,11 +95,13 @@ export default class SeverityFacet extends React.PureComponent { render() { const { fetching, open, severities, stats = {} } = this.props; const values = severities.map((severity) => translate('severity', severity)); + const headerId = `facet_${this.property}`; return ( { {open && ( <> - {SEVERITIES.map(this.renderItem)} + {SEVERITIES.map(this.renderItem)} )} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx index c5b9c80cdf0..68bcefe5b7a 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx @@ -157,6 +157,10 @@ export default class StandardFacet extends React.PureComponent { ]; }; + getFacetHeaderId = (property: string) => { + return `facet_${property}`; + }; + handleHeaderClick = () => { this.props.onToggle(this.property); }; @@ -265,7 +269,7 @@ export default class StandardFacet extends React.PureComponent { stats: any, values: string[], categories: string[], - listLabel: ValuesProp, + listKey: ValuesProp, renderName: (standards: Standards, category: string) => React.ReactNode, renderTooltip: (standards: Standards, category: string) => string, onClick: (x: string, multiple?: boolean) => void @@ -283,7 +287,7 @@ export default class StandardFacet extends React.PureComponent { }; return ( - + {categories.map((category) => ( { const allItemShown = limitedList.length + selectedBelowLimit.length === sortedItems.length; return ( <> - + {limitedList.map((item) => ( { {selectedBelowLimit.length > 0 && ( <> {!allItemShown &&
      ⋯
      } - + {selectedBelowLimit.map((item) => ( { { { { return ( { render() { const { statuses, stats = {}, forceShow, fetching, open } = this.props; const values = statuses.map((status) => translate('issue.status', status)); + const headerId = `facet_${this.property}`; if (values.length < 1 && !forceShow) { return null; @@ -104,6 +105,7 @@ export default class StatusFacet extends React.PureComponent { { {open && ( <> - {STATUSES.map(this.renderItem)} + {STATUSES.map(this.renderItem)} )} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx index 51ea8f58f4b..ee224035d00 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx @@ -102,6 +102,7 @@ export default class TypeFacet extends React.PureComponent { render() { const { types, stats = {}, forceShow, open, fetching } = this.props; const values = types.map((type) => translate('issue.type', type)); + const typeFacetHeaderId = `facet_${this.property}`; if (values.length < 1 && !forceShow) { return null; @@ -111,6 +112,7 @@ export default class TypeFacet extends React.PureComponent { { {open && ( <> - + {ISSUE_TYPES.filter((t) => t !== 'SECURITY_HOTSPOT').map(this.renderItem)} diff --git a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx index d52b7ac367c..8a6ff37de21 100644 --- a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx +++ b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx @@ -92,9 +92,10 @@ export const ui = { showFiltersButton: (showMore = true) => byRole('button', { name: `issues.show_${showMore ? 'more' : 'less'}_filters` }), - ruleFacetList: byRole('list', { name: 'rules' }), - languageFacetList: byRole('list', { name: 'languages' }), + ruleFacetList: byRole('list', { name: 'issues.facet.rules' }), + languageFacetList: byRole('list', { name: 'issues.facet.languages' }), ruleFacetSearch: byRole('searchbox', { name: 'search.search_for_rules' }), + inNewCodeFilter: byRole('checkbox', { name: 'issues.new_code' }), }; export async function waitOnDataLoaded() { diff --git a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx index 512b3e59f6f..9f319649f35 100644 --- a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx +++ b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx @@ -19,8 +19,8 @@ */ import classNames from 'classnames'; import * as React from 'react'; -import { Button, ButtonLink } from '../../components/controls/buttons'; import HelpTooltip from '../../components/controls/HelpTooltip'; +import { Button, ButtonLink } from '../../components/controls/buttons'; import OpenCloseIcon from '../../components/icons/OpenCloseIcon'; import DeferredSpinner from '../../components/ui/DeferredSpinner'; import { translate, translateWithParameters } from '../../helpers/l10n'; @@ -33,6 +33,7 @@ interface Props { disabled?: boolean; disabledHelper?: string; name: string; + id: string; onClear?: () => void; onClick?: () => void; open: boolean; @@ -70,7 +71,7 @@ export default class FacetHeader extends React.PureComponent { } render() { - const { disabled, values, disabledHelper, name, open, children, fetching } = this.props; + const { disabled, values, disabledHelper, name, open, children, fetching, id } = this.props; const showClearButton = values != null && values.length > 0 && this.props.onClear != null; const header = disabled ? ( @@ -99,6 +100,7 @@ export default class FacetHeader extends React.PureComponent { onClick={this.handleClick} aria-expanded={open} tabIndex={0} + id={id} > {header} @@ -106,7 +108,7 @@ export default class FacetHeader extends React.PureComponent { {this.renderHelper()} ) : ( - + {header} {this.renderHelper()} diff --git a/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx b/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx index e86348de142..980b66e1580 100644 --- a/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx +++ b/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx @@ -19,14 +19,22 @@ */ import * as React from 'react'; -export interface FacetItemsListProps { - children?: React.ReactNode; - label: string; -} +export type FacetItemsListProps = + | { + children?: React.ReactNode; + labelledby: string; + label?: never; + } + | { + children?: React.ReactNode; + labelledby?: never; + label: string; + }; -export default function FacetItemsList({ children, label }: FacetItemsListProps) { +export default function FacetItemsList({ children, labelledby, label }: FacetItemsListProps) { + const props = labelledby ? { 'aria-labelledby': labelledby } : { 'aria-label': label }; return ( -
      +
      {children}
      ); diff --git a/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx b/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx index b49859ee10f..2953bdf44e6 100644 --- a/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx +++ b/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx @@ -231,6 +231,10 @@ export default class ListStyleFacet extends React.Component, State { + return `facet_${property}`; + }; + showFullList = () => { this.setState({ showFullList: true }); }; @@ -275,7 +279,7 @@ export default class ListStyleFacet extends React.Component, State - + {limitedList.map((item) => ( extends React.Component, State 0 && ( <>
      ⋯
      - + {selectedBelowLimit.map((item) => ( extends React.Component, State - + {searchResults.map((result) => this.renderSearchResult(result))} {searchMaxResults && ( @@ -419,6 +423,7 @@ export default class ListStyleFacet extends React.Component, State { const onFacetClick = jest.fn(); - renderFacet(undefined, undefined, undefined, { onClick: onFacetClick }); + renderFacet(undefined, undefined, { onClick: onFacetClick }); // Start closed. let facetHeader = screen.getByRole('button', { name: 'foo', expanded: false }); @@ -78,7 +78,6 @@ it('should correctly render a disabled header', () => { function renderFacet( facetBoxProps: Partial = {}, facetHeaderProps: Partial = {}, - facetItemListProps: Partial = {}, facetItemProps: Partial = {} ) { function Facet() { @@ -86,10 +85,12 @@ function renderFacet( const [values, setValues] = React.useState(facetHeaderProps.values ?? undefined); const property = 'foo'; + const headerId = `facet_${property}`; return ( setOpen(!open)} onClear={() => setValues(undefined)} @@ -97,7 +98,7 @@ function renderFacet( /> {open && ( - +