]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22302 Fix a11y issues on Issues page
authorstanislavh <stanislav.honcharov@sonarsource.com>
Wed, 20 Nov 2024 12:57:49 +0000 (13:57 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 22 Nov 2024 20:03:09 +0000 (20:03 +0000)
22 files changed:
server/sonar-web/src/main/js/apps/coding-rules/components/AvailableSinceFacet.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RepositoryFacet.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/TagFacet.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/IssueStatusFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/PeriodFilter.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/PrioritizedRuleFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/ScopeFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/SimpleListStyleFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/StandardFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/VariantFacet.tsx
server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx
server/sonar-web/src/main/js/components/facets/Facet.tsx
server/sonar-web/src/main/js/components/issue/components/IssueView.tsx
server/sonar-web/src/main/js/design-system/components/FacetBox.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 94d2c7b943c4dff5d8e0d0600789b7988cdd5e54..8ebf490cb0c6fa8faf389d72218ddd6986940945 100644 (file)
@@ -50,10 +50,10 @@ class AvailableSinceFacet extends React.PureComponent<Props & WrappedComponentPr
     const { open, value } = this.props;
     const headerId = `facet_${this.property}`;
     const count = value ? 1 : undefined;
+
     return (
       <FacetBox
         className="it__search-navigator-facet-box"
-        clearIconLabel={translate('clear')}
         data-property={this.property}
         id={headerId}
         name={translate('coding_rules.facet.available_since')}
index 34fba4ff3cd269f465ba3faa03d55367df9e367f..c45ec026518ce27034595fc75fd672b339dfb510 100644 (file)
@@ -162,7 +162,6 @@ export default class ProfileFacet extends React.PureComponent<Props> {
 
     const property = 'profile';
     const headerId = `facet_${property}`;
-
     const count = value ? 1 : undefined;
 
     return (
@@ -174,7 +173,6 @@ export default class ProfileFacet extends React.PureComponent<Props> {
         onClear={this.handleClear}
         onClick={this.handleHeaderClick}
         open={open}
-        clearIconLabel={translate('clear')}
         count={count}
         help={
           <DocHelpTooltip
index 359a10585c8c004c4d56b9e4d963df106f35c5b0..30a8b22c5ca3ebc40a421b60f5461a8c443c6625 100644 (file)
@@ -90,8 +90,6 @@ class RepositoryFacet extends React.PureComponent<Props> {
     return (
       <ListStyleFacet<string>
         facetHeader={translate('coding_rules.facet.repositories')}
-        showMoreAriaLabel={translate('coding_rules.facet.repository.show_more')}
-        showLessAriaLabel={translate('coding_rules.facet.repository.show_less')}
         fetching={false}
         getFacetItemText={this.renderTextName}
         getSearchResultKey={(rep) => rep}
index 0e623377c552cb9fe465c13ea6ec5d238775a983..1b0419347a5711aec03b6d379a261e35392e4cf6 100644 (file)
@@ -54,8 +54,6 @@ export default class TagFacet extends React.PureComponent<BasicProps> {
         getFacetItemText={this.getTagName}
         getSearchResultKey={(tag) => tag}
         getSearchResultText={(tag) => tag}
-        showMoreAriaLabel={translate('coding_rules.facet.tag.show_more')}
-        showLessAriaLabel={translate('coding_rules.facet.tag.show_less')}
         onChange={this.props.onChange}
         onSearch={this.handleSearch}
         onToggle={this.props.onToggle}
index b135db1a33c18743c8fc9189b60de5f8ae421ad4..e0919ca6431b294817069025d1416d897135927c 100644 (file)
@@ -933,7 +933,7 @@ export class App extends React.PureComponent<Props, State> {
         <Checkbox
           ariaLabel={translate('issues.select_all_issues')}
           checked={thirdState ? 'indeterminate' : isChecked}
-          className="sw-mr-2"
+          className="sw-ml-4 sw-mr-3"
           id="issues-selection"
           isDisabled={issues.length === 0}
           onCheck={this.handleCheckAll}
index 1a4b124fe7561ff3f931cf700a29beb46921ba1e..6e4c4d7e095973cb2768aee6765a9b2cc473ee22 100644 (file)
@@ -60,13 +60,11 @@ export default class IssuesList extends React.PureComponent<Props, State> {
     }
   }
 
-  renderIssueComponentList = (issues: Issue[], index: number) => {
+  renderIssueComponentList = ([key, issues]: [string, Issue[]]) => {
     const { branchLike, checked, component, openPopup, selectedIssue } = this.props;
     return (
-      <React.Fragment key={index}>
-        <li>
-          <ComponentBreadcrumbs component={component} issue={issues[0]} />
-        </li>
+      <li key={key}>
+        <ComponentBreadcrumbs component={component} issue={issues[0]} />
         <ul>
           {issues.map((issue) => (
             <IssueItem
@@ -83,7 +81,7 @@ export default class IssuesList extends React.PureComponent<Props, State> {
             />
           ))}
         </ul>
-      </React.Fragment>
+      </li>
     );
   };
 
@@ -101,6 +99,6 @@ export default class IssuesList extends React.PureComponent<Props, State> {
 
     const issuesByComponent = groupBy(issues, (issue) => `(${issue.component} : ${issue.branch})`);
 
-    return <ul>{Object.values(issuesByComponent).map(this.renderIssueComponentList)}</ul>;
+    return <ul>{Object.entries(issuesByComponent).map(this.renderIssueComponentList)}</ul>;
   }
 }
index 8bec820112c252fe9baf7f54dbb82f60793831bf..50cbc49333302834a97d08f300618ff33b459f7c 100644 (file)
@@ -279,7 +279,6 @@ export class CreationDateFacetClass extends React.PureComponent<Props & WrappedC
     return (
       <FacetBox
         className="it__search-navigator-facet-box it__search-navigator-facet-header"
-        clearIconLabel={translate('clear')}
         count={count}
         countLabel={translateWithParameters('x_selected', count)}
         data-property={this.property}
index 51570eff63c5dd54bc57c3a584a414e027b09e60..48cfd31b54fc1cc392fed3dd00484db77423228f 100644 (file)
@@ -53,7 +53,6 @@ export function IssueStatusFacet(props: Readonly<Props>) {
   return (
     <FacetBox
       className="it__search-navigator-facet-box it__search-navigator-facet-header"
-      clearIconLabel={intl.formatMessage({ id: 'clear' })}
       count={nbSelectedItems}
       countLabel={intl.formatMessage({ id: 'x_selected' }, { '0': nbSelectedItems })}
       data-property={property}
index 9f331a529f4cbfc2fef312d0506abbc17d46dff4..fb1a2278d9e405c8cf67805776d2b221e7a4f1c4 100644 (file)
@@ -288,6 +288,7 @@ export class ListStyleFacet<S> extends React.Component<Props<S>, State<S>> {
       showMoreAriaLabel,
       showLessAriaLabel,
       values,
+      facetHeader,
     } = this.props;
 
     if (!stats) {
@@ -360,9 +361,13 @@ export class ListStyleFacet<S> extends React.Component<Props<S>, State<S>> {
         <ListStyleFacetFooter
           nbShown={limitedList.length + selectedBelowLimit.length}
           showLess={this.state.showFullList ? this.hideFullList : undefined}
-          showLessAriaLabel={showLessAriaLabel}
+          showMoreAriaLabel={
+            showMoreAriaLabel ?? translateWithParameters('show_more_filter_x', facetHeader)
+          }
+          showLessAriaLabel={
+            showLessAriaLabel ?? translateWithParameters('show_less_filter_x', facetHeader)
+          }
           showMore={this.showFullList}
-          showMoreAriaLabel={showMoreAriaLabel}
           total={sortedItems.length}
         />
 
@@ -481,7 +486,6 @@ export class ListStyleFacet<S> extends React.Component<Props<S>, State<S>> {
     return (
       <FacetBox
         className="it__search-navigator-facet-box it__search-navigator-facet-header"
-        clearIconLabel={translate('clear')}
         count={nbSelectedItems}
         countLabel={translateWithParameters('x_selected', nbSelectedItems)}
         data-property={property}
index e512b5ab28a4a3a48acc7b6065c68d821f0f1bf5..146b49070548c3d945add022dda9d55b40898350 100644 (file)
@@ -51,16 +51,17 @@ export function PeriodFilter(props: PeriodFilterProps) {
   }, [newCodeSelected, onChange]);
 
   return (
-    <FacetItemsList label={translate('issues.facet', PROPERTY)}>
-      <FacetItem
-        active={newCodeSelected}
-        className="it__search-navigator-facet"
-        name={translate('issues.new_code')}
-        onClick={handleClick}
-        value={newCodeSelected ? CodeScope.New : CodeScope.Overall}
-      />
-
+    <>
+      <FacetItemsList label={translate('issues.facet', PROPERTY)}>
+        <FacetItem
+          active={newCodeSelected}
+          className="it__search-navigator-facet"
+          name={translate('issues.new_code')}
+          onClick={handleClick}
+          value={newCodeSelected ? CodeScope.New : CodeScope.Overall}
+        />
+      </FacetItemsList>
       <BasicSeparator className="sw-mb-5 sw-mt-4" />
-    </FacetItemsList>
+    </>
   );
 }
index c9218d3fcc4ad6df02ead0098cb97fb14e68d1bf..d6afbdfefab3018db595f881266e77234d878dc7 100644 (file)
@@ -43,7 +43,6 @@ export function PrioritizedRuleFacet(props: Readonly<PrioritizedRuleFacetProps>)
   return (
     <FacetBox
       className="it__search-navigator-facet-box it__search-navigator-facet-header"
-      clearIconLabel={intl.formatMessage({ id: 'clear' })}
       count={value ? 1 : 0}
       onClear={() => props.onChange({ [property]: undefined })}
       onClick={() => onToggle(property)}
index ee9881a430796db40b0a7e85d4e32403417b18a2..b6eedff37b5e4794761c6ae7368d46391cc4757c 100644 (file)
@@ -20,6 +20,7 @@
 
 import { IconProject, Spinner } from '@sonarsource/echoes-react';
 import { omit } from 'lodash';
+import { useIntl } from 'react-intl';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
 import { MetricKey } from '~sonar-aligned/types/metrics';
 import { getTree, searchProjects } from '../../../api/components';
@@ -61,6 +62,7 @@ export function ProjectFacet(props: Readonly<Props>) {
     referencedComponents,
     stats,
   } = props;
+  const intl = useIntl();
 
   const handleSearch = (
     query: string,
@@ -132,7 +134,7 @@ export function ProjectFacet(props: Readonly<Props>) {
 
   return (
     <ListStyleFacet<SearchedProject>
-      facetHeader={translate('issues.facet.projects')}
+      facetHeader={intl.formatMessage({ id: 'issues.facet.projects' })}
       fetching={fetching}
       getFacetItemText={getProjectName}
       getSearchResultKey={(project) => project.key}
index cb88c0ab70d7cb44c8aac4141811573e2a579e11..36c132607f849581197a18e461763f7fcceda023 100644 (file)
@@ -20,6 +20,7 @@
 
 import { IconFile, IconFileCode } from '@sonarsource/echoes-react';
 import { without } from 'lodash';
+import { useIntl } from 'react-intl';
 import { FacetBox, FacetItem } from '~design-system';
 import { SOURCE_SCOPES } from '../../../helpers/constants';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -39,6 +40,7 @@ export interface ScopeFacetProps {
 
 export function ScopeFacet(props: ScopeFacetProps) {
   const { fetching, open, scopes = [], stats = {} } = props;
+  const intl = useIntl();
 
   const nbSelectableItems = SOURCE_SCOPES.filter(({ scope }) => stats[scope]).length;
   const nbSelectedItems = scopes.length;
@@ -48,13 +50,12 @@ export function ScopeFacet(props: ScopeFacetProps) {
   return (
     <FacetBox
       className="it__search-navigator-facet-box it__search-navigator-facet-header"
-      clearIconLabel={translate('clear')}
       count={nbSelectedItems}
       countLabel={translateWithParameters('x_selected', nbSelectedItems)}
       data-property={property}
       id={headerId}
       loading={fetching}
-      name={translate('issues.facet.scopes')}
+      name={intl.formatMessage({ id: 'issues.facet.scopes' })}
       onClear={() => props.onChange({ scopes: [] })}
       onClick={() => props.onToggle('scopes')}
       open={open}
index d92b019c58ac4b56660da1ea37efe62f575baa2c..c5bb7ad0239c893bb538d4294c924e7e73774218 100644 (file)
@@ -20,6 +20,7 @@
 
 import { without } from 'lodash';
 import * as React from 'react';
+import { useIntl } from 'react-intl';
 import { FacetBox, FacetItem } from '~design-system';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { Dict } from '../../../types/types';
@@ -60,6 +61,7 @@ export function SimpleListStyleFacet(props: Props) {
     help,
     renderIcon,
   } = props;
+  const intl = useIntl();
 
   const nbSelectableItems = listItems.filter((item) => stats[item]).length;
   const nbSelectedItems = selectedItems.length;
@@ -68,13 +70,12 @@ export function SimpleListStyleFacet(props: Props) {
   return (
     <FacetBox
       className="it__search-navigator-facet-box it__search-navigator-facet-header"
-      clearIconLabel={translate('clear')}
       count={nbSelectedItems}
       countLabel={translateWithParameters('x_selected', nbSelectedItems)}
       data-property={property}
       id={headerId}
       loading={fetching}
-      name={translate('issues.facet', property)}
+      name={intl.formatMessage({ id: `issues.facet.${property}` })}
       onClear={() => props.onChange({ [property]: [] })}
       onClick={() => props.onToggle(property)}
       open={open}
index 5c127a1df4b09389353c4e801fb1fb9491456a78..2cd636e3fb703c233e6ba2eaaec453d8e4b17914 100644 (file)
@@ -523,13 +523,11 @@ export class StandardFacet extends React.PureComponent<Props, State> {
 
   render() {
     const { open } = this.props;
-
     const count = this.getValues().length;
 
     return (
       <FacetBox
         className="it__search-navigator-facet-box it__search-navigator-facet-header"
-        clearIconLabel={translate('clear')}
         count={count}
         countLabel={translateWithParameters('x_selected', count)}
         data-property={this.property}
index a0ed6e0403564bfea0e830d296499e4dd4c1888c..d4a2fc9c13d09792fd474dd3a6ade93d35bd32e8 100644 (file)
@@ -119,7 +119,6 @@ export class TypeFacet extends React.PureComponent<Props> {
     return (
       <FacetBox
         className="it__search-navigator-facet-box it__search-navigator-facet-header"
-        clearIconLabel={translate('clear')}
         count={nbSelectedItems}
         countLabel={translateWithParameters('x_selected', nbSelectedItems)}
         data-property={this.property}
index 11fa8bce2fa983d5810ef98aca88d71d66808db5..ec3078888f61b2b3e567d876000d95fdf0c89aca 100644 (file)
@@ -77,7 +77,6 @@ export function VariantFacet(props: VariantFacetProps) {
   return (
     <FacetBox
       className="it__search-navigator-facet-box it__search-navigator-facet-header"
-      clearIconLabel={translate('clear')}
       count={nbSelectedItems}
       countLabel={translateWithParameters('x_selected', nbSelectedItems)}
       data-property={FACET_NAME}
index 229ec9cfe1f98d8269a86383f550bd34a20169bb..8858bf99ec321291aaf4033f0cd7bac5fb924773 100644 (file)
@@ -20,6 +20,7 @@
 
 import { uniqBy } from 'lodash';
 import * as React from 'react';
+import { useIntl } from 'react-intl';
 import { RawQuery } from '~sonar-aligned/types/router';
 import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
 import { translate } from '../../../helpers/l10n';
@@ -40,6 +41,7 @@ interface Props {
 
 export function LanguagesFilter(props: Props) {
   const { facet, languages, loadSearchResultCount, query, onQueryChange, value = [] } = props;
+  const intl = useIntl();
 
   const searchOptions = React.useMemo(() => {
     // add any language that presents in the facet, but might not be installed
@@ -103,7 +105,7 @@ export function LanguagesFilter(props: Props) {
 
   return (
     <ListStyleFacet<Language>
-      facetHeader={translate('projects.facets.languages')}
+      facetHeader={intl.formatMessage({ id: 'projects.facets.languages' })}
       fetching={false}
       getFacetItemText={renderLanguageName}
       getSearchResultKey={(language) => language.key}
index 365af7843f0448cc3eeefe401ee47c31ad7bf2d2..f29cbaf70d51d5d2ec3050bf339aaa98b393e638 100644 (file)
@@ -131,7 +131,6 @@ export default class Facet extends React.PureComponent<Props> {
         })}
         data-property={property}
         loading={fetching}
-        clearIconLabel={translate('clear')}
         count={values.length}
         id={headerId}
         name={headerName ?? translate('coding_rules.facet', property)}
index 8dd3ddf546814707d8ad46cd40c2ac62eaec4416..14f0a1b5027a4f5412fbc59932a08ab9ba5200f3 100644 (file)
@@ -157,11 +157,9 @@ export default function IssueView(props: Readonly<Props>) {
       className={classNames('it__issue-item sw-p-3 sw-mb-4 sw-rounded-1 sw-bg-white', {
         selected,
       })}
-      role="region"
-      aria-label={issue.message}
       ref={nodeRef}
     >
-      <div className="sw-flex sw-gap-3">
+      <section aria-label={issue.message} className="sw-flex sw-gap-3">
         {hasCheckbox && (
           <span className="sw-mt-1/2 sw-ml-1 sw-self-start">
             <Checkbox
@@ -212,7 +210,7 @@ export default function IssueView(props: Readonly<Props>) {
             <IssueMetaBar issue={issue} />
           </div>
         </div>
-      </div>
+      </section>
     </IssueItem>
   );
 }
index c93ac18933fcbab836c046f728c0ff4b11bbea69..84001842fdc8c1393709ab0990e61e9cfcc7e7ef 100644 (file)
@@ -23,6 +23,7 @@ import { Text } from '@sonarsource/echoes-react';
 import classNames from 'classnames';
 import { uniqueId } from 'lodash';
 import * as React from 'react';
+import { useIntl } from 'react-intl';
 import tw from 'twin.macro';
 import { themeColor } from '../helpers';
 import { BareButton } from '../sonar-aligned/components/buttons';
@@ -79,6 +80,7 @@ export function FacetBox(props: FacetBoxProps) {
     open = false,
     tooltipComponent,
   } = props;
+  const intl = useIntl();
 
   const clearable = !disabled && Boolean(onClear) && count !== undefined && count > 0;
   const counter = count ?? 0;
@@ -148,7 +150,9 @@ export function FacetBox(props: FacetBoxProps) {
               <Tooltip content={clearIconLabel}>
                 <ClearIcon
                   Icon={CloseIcon}
-                  aria-label={clearIconLabel ?? ''}
+                  aria-label={
+                    clearIconLabel ?? intl.formatMessage({ id: 'clear_x_filter' }, { '0': name })
+                  }
                   data-testid={`clear-${name}`}
                   onClick={onClear}
                   size="small"
index 2a95acd3d556feffb28717d4b911dd1121a25fb8..b802dd322a37bcc9c512d06debc2c48fc1e6b413 100644 (file)
@@ -345,7 +345,9 @@ short_number_suffix.g=G
 short_number_suffix.k=k
 short_number_suffix.m=M
 show_less=Show Less
+show_less_filter_x=Show less results for "{0}" filter
 show_more=Show More
+show_more_filter_x=Show more results for "{0}" filter
 show_all=Show All
 show_them=Show Them
 should_be_unique=Should be unique
@@ -2776,15 +2778,7 @@ coding_rules.facet.template=Template
 coding_rules.facet.rule_key=Rule
 coding_rules.facet.types=Type
 coding_rules.facet.prioritizedRule=Prioritized Rules
-
-coding_rules.facet.language.show_more=Show more languages
-coding_rules.facet.language.show_less=Show less languages
-coding_rules.facet.tag.show_more=Show more tags
-coding_rules.facet.tag.show_less=Show less tags
-coding_rules.facet.repository.show_more=Show more repositories
-coding_rules.facet.repository.show_less=Show less repositories
 coding_rules.facet.security_hotspots.show_only=Show Security Hotspots Only
-
 coding_rules.facets.languages=Languages
 coding_rules.facets.tags=Tags
 coding_rules.facets.repositories=Repositories