aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstanislavh <stanislav.honcharov@sonarsource.com>2024-11-28 15:07:52 +0100
committersonartech <sonartech@sonarsource.com>2024-11-29 20:03:05 +0000
commit86455202929335f50ad609427eef71f43a2e753c (patch)
treef3c6edf1eaf78b92658cc021fc386f598969ac2f
parent8c5df648a3ac7917027ad002d3a59403d90a4ea6 (diff)
downloadsonarqube-86455202929335f50ad609427eef71f43a2e753c.tar.gz
sonarqube-86455202929335f50ad609427eef71f43a2e753c.zip
SONAR-22326 Fix a11y issues in header and project branch selector
-rw-r--r--server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx9
-rw-r--r--server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResults.tsx22
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx1
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx68
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties3
5 files changed, 75 insertions, 28 deletions
diff --git a/server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx b/server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx
index 5d3c988f100..684ef20ec50 100644
--- a/server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx
+++ b/server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx
@@ -18,10 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { ButtonIcon, ButtonVariety, IconSearch } from '@sonarsource/echoes-react';
+import { ButtonIcon, ButtonVariety, IconSearch, Text } from '@sonarsource/echoes-react';
import { debounce, isEmpty, uniqBy } from 'lodash';
import * as React from 'react';
-import { DropdownMenu, InputSearch, Popup, PopupZLevel, TextMuted } from '~design-system';
+import { DropdownMenu, InputSearch, Popup, PopupZLevel } from '~design-system';
import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
import { ComponentQualifier } from '~sonar-aligned/types/component';
import { Router } from '~sonar-aligned/types/router';
@@ -345,7 +345,7 @@ export class GlobalSearch extends React.PureComponent<Props, State> {
);
renderNoResults = () => (
- <div className="sw-px-3 sw-py-2" aria-live="assertive">
+ <div className="sw-px-3 sw-py-2">
{translateWithParameters('no_results_for_x', this.state.query)}
</div>
);
@@ -369,6 +369,7 @@ export class GlobalSearch extends React.PureComponent<Props, State> {
aria-owns="global-search-input"
>
<GlobalSearchResults
+ loading={loading}
query={query}
loadingMore={loadingMore}
more={more}
@@ -381,7 +382,7 @@ export class GlobalSearch extends React.PureComponent<Props, State> {
/>
{list.length > 0 && (
<li className="sw-px-3 sw-pt-1">
- <TextMuted text={translate('global_search.shortcut_hint')} />
+ <Text isSubdued>{translate('global_search.shortcut_hint')}</Text>
</li>
)}
</DropdownMenu>
diff --git a/server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResults.tsx b/server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResults.tsx
index 6714b6a437e..85d23c8a1d8 100644
--- a/server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResults.tsx
+++ b/server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResults.tsx
@@ -19,12 +19,14 @@
*/
import * as React from 'react';
+import { useIntl } from 'react-intl';
import { ItemDivider, ItemHeader } from '~design-system';
import { translate } from '../../../helpers/l10n';
import GlobalSearchShowMore from './GlobalSearchShowMore';
import { ComponentResult, More, Results, sortQualifiers } from './utils';
export interface Props {
+ loading: boolean;
loadingMore?: string;
more: More;
onMoreClick: (qualifier: string) => void;
@@ -37,6 +39,7 @@ export interface Props {
}
export default function GlobalSearchResults(props: Props): React.ReactElement<Props> {
+ const intl = useIntl();
const qualifiers = Object.keys(props.results);
const renderedComponents: React.ReactNode[] = [];
const allowMore = props.query.length !== 1;
@@ -72,5 +75,22 @@ export default function GlobalSearchResults(props: Props): React.ReactElement<Pr
}
});
- return renderedComponents.length > 0 ? <>{renderedComponents}</> : props.renderNoResults();
+ const resultCount = Object.values(props.results).reduce(
+ (acc, components) => acc + (components?.length ?? 0),
+ 0,
+ );
+
+ return (
+ <>
+ <output aria-busy={props.loading || Boolean(props.loadingMore)}>
+ {renderedComponents.length === 0 && props.renderNoResults()}
+ {renderedComponents.length > 0 && (
+ <span className="sw-sr-only">
+ {intl.formatMessage({ id: 'results_shown_x' }, { count: resultCount })}
+ </span>
+ )}
+ </output>
+ {renderedComponents.length > 0 && renderedComponents}
+ </>
+ );
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
index 7faaff1de44..1c608d237b7 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
@@ -173,6 +173,7 @@ export class Menu extends React.PureComponent<Props, State> {
searchInputAriaLabel={translate('search_verb')}
/>
<MenuItemList
+ search={query}
branchLikeTree={branchLikesToDisplayTree}
hasResults={hasResults}
onSelect={this.handleOnSelect}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx
index 5b84a3ccf6a..7315a367f50 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
+import { useIntl } from 'react-intl';
import { HelperHintIcon, ItemDivider, ItemHeader } from '~design-system';
import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip';
import { getBranchLikeKey, isSameBranchLike } from '../../../../../helpers/branch-like';
@@ -31,10 +32,12 @@ export interface MenuItemListProps {
branchLikeTree: BranchLikeTree;
hasResults: boolean;
onSelect: (branchLike: BranchLike) => void;
+ search: string;
selectedBranchLike: BranchLike | undefined;
}
export function MenuItemList(props: MenuItemListProps) {
+ const intl = useIntl();
let selectedNode: HTMLLIElement | null = null;
React.useEffect(() => {
@@ -44,7 +47,7 @@ export function MenuItemList(props: MenuItemListProps) {
}
});
- const { branchLikeTree, hasResults, onSelect, selectedBranchLike } = props;
+ const { branchLikeTree, hasResults, onSelect, selectedBranchLike, search } = props;
const renderItem = (branchLike: BranchLike, indent = false) => (
<MenuItem
@@ -57,46 +60,65 @@ export function MenuItemList(props: MenuItemListProps) {
/>
);
- const branches = [branchLikeTree.mainBranchTree, ...branchLikeTree.branchTree];
+ const branches = [branchLikeTree.mainBranchTree, ...branchLikeTree.branchTree].filter(isDefined);
+ const total =
+ branches.length +
+ branches.reduce((t, branchTree) => t + (branchTree?.pullRequests.length ?? 0), 0) +
+ branchLikeTree.parentlessPullRequests.length +
+ branchLikeTree.orphanPullRequests.length;
return (
- <ul className="item-list sw-overflow-y-auto sw-overflow-x-hidden">
- {!hasResults && (
- <div className="sw-px-3 sw-py-2">
- <span>{translate('no_results')}</span>
- </div>
- )}
+ <ul
+ aria-label={`- ${translate('branch_like_navigation.list')}`}
+ className="item-list sw-overflow-y-auto sw-overflow-x-hidden"
+ >
+ <output>
+ {!hasResults && (
+ <div className="sw-px-3 sw-py-2">
+ <span>{intl.formatMessage({ id: 'no_results_for_x' }, { '0': search })}</span>
+ </div>
+ )}
+ {hasResults && (
+ <span className="sw-sr-only">
+ {intl.formatMessage({ id: 'results_shown_x' }, { count: total })}
+ </span>
+ )}
+ </output>
{/* BRANCHES & PR */}
- {branches.filter(isDefined).map((tree, treeIndex) => (
+ {branches.map((tree, treeIndex) => (
<React.Fragment key={getBranchLikeKey(tree.branch)}>
{renderItem(tree.branch)}
{tree.pullRequests.length > 0 && (
- <>
- <ItemDivider />
- <ItemHeader>{translate('branch_like_navigation.pull_requests')}</ItemHeader>
- <ItemDivider />
+ <ul
+ aria-label={` - ${intl.formatMessage({ id: 'branch_like_navigation.pull_requests_targeting' }, { branch: tree.branch.name })}`}
+ >
+ <ItemDivider aria-hidden />
+ <ItemHeader aria-hidden>
+ {translate('branch_like_navigation.pull_requests')}
+ </ItemHeader>
+ <ItemDivider aria-hidden />
{tree.pullRequests.map((pr) => renderItem(pr, true))}
{tree.pullRequests.length > 0 && treeIndex !== branches.length - 1 && <ItemDivider />}
- </>
+ </ul>
)}
</React.Fragment>
))}
{/* PARENTLESS PR (for display during search) */}
{branchLikeTree.parentlessPullRequests.length > 0 && (
- <>
- <ItemDivider />
- <ItemHeader>{translate('branch_like_navigation.pull_requests')}</ItemHeader>
- <ItemDivider />
+ <ul aria-label={` - ${translate('branch_like_navigation.pull_requests')}`}>
+ <ItemDivider aria-hidden />
+ <ItemHeader aria-hidden>{translate('branch_like_navigation.pull_requests')}</ItemHeader>
+ <ItemDivider aria-hidden />
{branchLikeTree.parentlessPullRequests.map((pr) => renderItem(pr))}
- </>
+ </ul>
)}
{/* ORPHAN PR */}
{branchLikeTree.orphanPullRequests.length > 0 && (
- <>
- <ItemDivider />
+ <ul aria-label={` - ${translate('branch_like_navigation.orphan_pull_requests')}`}>
+ <ItemDivider aria-hidden />
<ItemHeader>
{translate('branch_like_navigation.orphan_pull_requests')}
<HelpTooltip
@@ -106,9 +128,9 @@ export function MenuItemList(props: MenuItemListProps) {
<HelperHintIcon />
</HelpTooltip>
</ItemHeader>
- <ItemDivider />
+ <ItemDivider aria-hidden />
{branchLikeTree.orphanPullRequests.map((pr) => renderItem(pr))}
- </>
+ </ul>
)}
</ul>
);
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 21d5616bea8..c4f1ee47089 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -334,6 +334,7 @@ new_violations=New violations
new_window=New window
no_data=No data
no_measure_value_x=No measure value for {0}
+results_shown_x={count} results shown
no_results=No results
no_results_for_x=No results for "{0}"
no_results_search=We couldn't find any results matching selected criteria.
@@ -5545,7 +5546,9 @@ branches.see_the_pr_on_x=See the PR on {0}
#------------------------------------------------------------------------------
branch_like_navigation.manage=Manage branches and Pull Requests
branch_like_navigation.search_for_branch_like=Search for branches or Pull Requests...
+branch_like_navigation.list=Branches and Pull Requests
branch_like_navigation.pull_requests=Pull Requests
+branch_like_navigation.pull_requests_targeting=Pull Requests targeting "{branch}"
branch_like_navigation.orphan_pull_requests=Orphan Pull Requests
branch_like_navigation.orphan_pull_requests.tooltip=When the base of a Pull Request is deleted, this Pull Request becomes orphan.
branch_like_navigation.for_merge_into_x_from_y=for merge into {target} from {branch}