]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22049 Align A11y components
authorIsmail Cherri <ismail.cherri@sonarsource.com>
Fri, 26 Apr 2024 14:50:13 +0000 (16:50 +0200)
committerMatteo Mara <matteo.mara@sonarsource.com>
Tue, 30 Apr 2024 08:59:04 +0000 (10:59 +0200)
29 files changed:
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
server/sonar-web/src/main/js/apps/account/Account.tsx
server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppRenderer.tsx
server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx
server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx
server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
server/sonar-web/src/main/js/components/a11y/A11yContext.tsx [deleted file]
server/sonar-web/src/main/js/components/a11y/A11yProvider.tsx [deleted file]
server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.css [deleted file]
server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.tsx [deleted file]
server/sonar-web/src/main/js/components/a11y/A11ySkipTarget.tsx [deleted file]
server/sonar-web/src/main/js/components/a11y/__tests__/A11yLinks-test.tsx [deleted file]
server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11yContext.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11yProvider.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipLinks.css [new file with mode: 0644]
server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipLinks.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipTarget.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/sonar-aligned/components/a11y/__tests__/A11yLinks-test.tsx [new file with mode: 0644]

index 50ee227d2b39e9a79bc6b4fc4c7ed79beb014437..022125a7d715d3ee390ac9056fe119ae2ea4c6d7 100644 (file)
@@ -23,8 +23,8 @@ import styled from '@emotion/styled';
 import { lightTheme, themeColor } from 'design-system';
 import * as React from 'react';
 import { Outlet, useLocation } from 'react-router-dom';
-import A11yProvider from '../../components/a11y/A11yProvider';
-import A11ySkipLinks from '../../components/a11y/A11ySkipLinks';
+import A11yProvider from '~sonar-aligned/components/a11y/A11yProvider';
+import A11ySkipLinks from '~sonar-aligned/components/a11y/A11ySkipLinks';
 import SuggestionsProvider from '../../components/embed-docs-modal/SuggestionsProvider';
 import NCDAutoUpdateMessage from '../../components/new-code-definition/NCDAutoUpdateMessage';
 import Workspace from '../../components/workspace/Workspace';
index 379c0fc5ce5b2bf0d3e2182e5723d9011f0be9c1..4c48662bcd6b8f2f922603f782343f51297e4d80 100644 (file)
@@ -19,7 +19,7 @@
  */
 import * as React from 'react';
 import { Outlet } from 'react-router-dom';
-import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { Component } from '../../types/types';
 import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
 import withComponentContext from './componentContext/withComponentContext';
index bab248526d638ab583a4dadb34b86fdd5e59f1f9..8521ac7a0b8d0d7e74c7fffcb0066ffd585d662d 100644 (file)
@@ -23,8 +23,8 @@ import * as React from 'react';
 import { createPortal } from 'react-dom';
 import { Helmet } from 'react-helmet-async';
 import { Outlet } from 'react-router-dom';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { useCurrentLoginUser } from '../../app/components/current-user/CurrentUserContext';
-import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
 import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import Nav from './components/Nav';
index 54f38997cee5e40dae74c2363a4df9bf31e4aabf..7b50ee8b8fbc114eb9715c0cab6eddc01357079a 100644 (file)
@@ -29,9 +29,9 @@ import {
 import { difference, intersection } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip';
 import { Location } from '~sonar-aligned/types/router';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import ListFooter from '../../../components/controls/ListFooter';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import AnalysisMissingInfoMessage from '../../../components/shared/AnalysisMissingInfoMessage';
index e9b9f0b09e314117965dc2b9f8163313dd1dc7d7..5c1e289b1513b7af5e8f263663ea8a2c5a3c0155 100644 (file)
@@ -30,13 +30,13 @@ import {
 import { keyBy } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
 import { Location, RawQuery, Router } from '~sonar-aligned/types/router';
 import { Profile, searchQualityProfiles } from '../../../api/quality-profiles';
 import { getRulesApp, searchRules } from '../../../api/rules';
 import { getValue } from '../../../api/settings';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import ListFooter from '../../../components/controls/ListFooter';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import '../../../components/search-navigator.css';
index 51ed61698e431714697ca70ae9cae5bc07a28ee2..13ec54ab50456012fb740cc9134c3a71ebeb98de 100644 (file)
  */
 import { Highlight, KeyboardHint } from 'design-system';
 import * as React from 'react';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like';
 import { MetricKey } from '~sonar-aligned/types/metrics';
 import { Router } from '~sonar-aligned/types/router';
 import { getComponentTree } from '../../../api/components';
 import { getMeasures } from '../../../api/measures';
 import SourceViewer from '../../../components/SourceViewer/SourceViewer';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import FilesCounter from '../../../components/ui/FilesCounter';
 import { isSameBranchLike } from '../../../helpers/branch-like';
 import { getComponentMeasureUniqueKey } from '../../../helpers/component';
index 243c4fb8f242d5334a9ee18095c53fbc29967b32..16950620500420e7487250ed70db5bd215665999 100644 (file)
  */
 import { Spinner } from 'design-system';
 import * as React from 'react';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like';
 import { getComponentLeaves } from '../../../api/components';
 import SourceViewer from '../../../components/SourceViewer/SourceViewer';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { isSameBranchLike } from '../../../helpers/branch-like';
 import { BranchLike } from '../../../types/branch-like';
 import { isFile } from '../../../types/component';
index efe68a77584e67464f96b97548ef3f2cab7e2a57..103d5ce02874ad97da156a92d2f9c172c26fc65d 100644 (file)
@@ -30,7 +30,7 @@ import {
   themeColor,
 } from 'design-system';
 import * as React from 'react';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { translate } from '../../../helpers/l10n';
 import useFollowScroll from '../../../hooks/useFollowScroll';
 import { Domain } from '../../../types/measures';
index 5870119ab8b60188bbb10bd0234a11433daf4cd6..56ac38b6b26f8ca414def41c9da04f53fc4c2700 100644 (file)
@@ -21,13 +21,13 @@ import classNames from 'classnames';
 import { LargeCenteredLayout } from 'design-system';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
 import { Location, Router } from '~sonar-aligned/types/router';
 import { getDopSettings } from '../../../api/dop-translation';
 import withAvailableFeatures, {
   WithAvailableFeaturesProps,
 } from '../../../app/components/available-features/withAvailableFeatures';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { translate } from '../../../helpers/l10n';
 import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
 import { DopSetting } from '../../../types/dop-translation';
index f825be933c9fb2043d8f6dbf29a9eff579562340..bce2730448a105f0bbe7f85f690c81fe7a940583 100644 (file)
@@ -34,6 +34,7 @@ import { keyBy, omit, without } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { FormattedMessage } from 'react-intl';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
 import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
@@ -42,7 +43,6 @@ import { listIssues, searchIssues } from '../../../api/issues';
 import { getRuleDetails } from '../../../api/rules';
 import withComponentContext from '../../../app/components/componentContext/withComponentContext';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import EmptySearch from '../../../components/common/EmptySearch';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import ListFooter from '../../../components/controls/ListFooter';
index 85243c10d47375355c5904d94006be33130b09b3..41c4648b96d6ed2a017ea1b1487b121b7ecf1103 100644 (file)
@@ -25,9 +25,9 @@ import {
   PageContentFontWrapper,
 } from 'design-system';
 import * as React from 'react';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { useLocation, useRouter } from '~sonar-aligned/components/hoc/withRouter';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import AnalysisMissingInfoMessage from '../../../components/shared/AnalysisMissingInfoMessage';
 import { parseDate } from '../../../helpers/dates';
 import { areCCTMeasuresComputed, isDiffMetric } from '../../../helpers/measures';
index fbee22df74972195dd1c39c2c8f0a08af4b34335..c42b8d6ba678be0ead55cb947cd8b6a61aac8b07 100644 (file)
@@ -26,8 +26,8 @@ import {
 } from 'design-system';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { MeasureHistory, ParsedAnalysis } from '../../../types/project-activity';
index 00ffe5de36a5a42781fa182829099bcd8e7032ae..cafa4b943a94393ba268e4b9f24e5e639eec8632 100644 (file)
@@ -35,8 +35,8 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { FormattedMessage } from 'react-intl';
 import { OptionProps, components } from 'react-select';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip';
-import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
 import DisableableSelectOption from '../../components/common/DisableableSelectOption';
 import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../helpers/l10n';
index b4aba107c4284d49eedd43511c98531c1771a09f..42a80d32cb1e4d1f8358302b0886ee6b93fdc955 100644 (file)
@@ -36,9 +36,9 @@ import {
 import { groupBy, orderBy } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip';
 import { Profile } from '../../api/quality-profiles';
-import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
 import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../helpers/l10n';
 import { getRulesUrl } from '../../helpers/urls';
index 6b920b73e1123f6759d8d7b3b8f013db5fa97305..5da6829970f67dc006c116506cff65df1af046cc 100644 (file)
@@ -31,6 +31,7 @@ import { keyBy, mapValues, omitBy, pick } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { useSearchParams } from 'react-router-dom';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
 import { MetricKey } from '~sonar-aligned/types/metrics';
@@ -38,7 +39,6 @@ import { Location, RawQuery, Router } from '~sonar-aligned/types/router';
 import { searchProjects } from '../../../api/components';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import '../../../components/search-navigator.css';
index fd56756bc52552d7ec60199e35771ab569e2d45e..a8abe63617b14c64fdb27671e73c59f5e5cd5393 100644 (file)
@@ -31,10 +31,10 @@ import {
 } from 'design-system';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { isBranch } from '~sonar-aligned/helpers/branch-like';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
 import { MetricKey } from '~sonar-aligned/types/metrics';
-import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
 import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../helpers/l10n';
 import useFollowScroll from '../../hooks/useFollowScroll';
index 5cb0fd1201ace191742bb61a9be63d9b46ac7f03..f61afe7235a11c192e7e9657a4fbcba4b4855255 100644 (file)
@@ -29,10 +29,10 @@ import { maxBy } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { Params, useParams } from 'react-router-dom';
+import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget';
 import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
 import { Location, Router } from '~sonar-aligned/types/router';
 import { fetchWebApi } from '../../../api/web-api';
-import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { WebApi } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/components/a11y/A11yContext.tsx b/server/sonar-web/src/main/js/components/a11y/A11yContext.tsx
deleted file mode 100644 (file)
index 7b74028..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import { createContext } from 'react';
-import { A11ySkipLink } from '../../types/types';
-
-export interface A11yContextShape {
-  addA11ySkipLink: (link: A11ySkipLink) => void;
-  removeA11ySkipLink: (link: A11ySkipLink) => void;
-  links: A11ySkipLink[];
-}
-
-export const A11yContext = createContext<A11yContextShape>({
-  addA11ySkipLink: () => {
-    /* Implemented by Provider */
-  },
-  removeA11ySkipLink: () => {
-    /* Implemented by Provider */
-  },
-  links: [],
-});
diff --git a/server/sonar-web/src/main/js/components/a11y/A11yProvider.tsx b/server/sonar-web/src/main/js/components/a11y/A11yProvider.tsx
deleted file mode 100644 (file)
index e74f034..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import { sortBy } from 'lodash';
-import * as React from 'react';
-import { A11ySkipLink } from '../../types/types';
-import { A11yContext } from './A11yContext';
-
-interface State {
-  links: A11ySkipLink[];
-}
-
-export default class A11yProvider extends React.Component<React.PropsWithChildren, State> {
-  keys: string[] = [];
-  state: State = { links: [] };
-
-  addA11ySkipLink = (link: A11ySkipLink) => {
-    this.setState((prevState) => {
-      const links = [...prevState.links];
-      links.push({ ...link, weight: link.weight || 0 });
-      return { links };
-    });
-  };
-
-  removeA11ySkipLink = (link: A11ySkipLink) => {
-    this.setState((prevState) => {
-      const links = prevState.links.filter((l) => l.key !== link.key);
-      return { links };
-    });
-  };
-
-  render() {
-    const links = sortBy(this.state.links, 'weight');
-    return (
-      <A11yContext.Provider
-        value={{
-          addA11ySkipLink: this.addA11ySkipLink,
-          links,
-          removeA11ySkipLink: this.removeA11ySkipLink,
-        }}
-      >
-        {this.props.children}
-      </A11yContext.Provider>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.css b/server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.css
deleted file mode 100644 (file)
index 75f5043..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-.a11y-skip-link {
-  width: 0;
-  height: 0;
-  padding: var(--gridSize);
-  position: absolute;
-  left: -9999px;
-  top: -9999px;
-  border: 0;
-  font-size: 1rem;
-  text-align: center;
-  z-index: 999;
-}
-
-.a11y-skip-link:focus {
-  width: auto;
-  height: auto;
-  left: 6px;
-  top: 6px;
-  color: white;
-  background-color: var(--globalNavBarBg);
-  text-decoration: underline;
-}
diff --git a/server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.tsx b/server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.tsx
deleted file mode 100644 (file)
index c9fd17c..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import { A11yContext } from './A11yContext';
-import './A11ySkipLinks.css';
-
-export default function A11ySkipLinks() {
-  return (
-    <A11yContext.Consumer>
-      {({ links }) =>
-        links.map((link) => (
-          <a className="a11y-skip-link" href={`#a11y_target__${link.key}`} key={link.key}>
-            {link.label}
-          </a>
-        ))
-      }
-    </A11yContext.Consumer>
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/a11y/A11ySkipTarget.tsx b/server/sonar-web/src/main/js/components/a11y/A11ySkipTarget.tsx
deleted file mode 100644 (file)
index 09f17b9..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import * as React from 'react';
-import { translate } from '../../helpers/l10n';
-import { A11ySkipLink } from '../../types/types';
-import { A11yContext } from './A11yContext';
-
-interface Props {
-  anchor: string;
-  label?: string;
-  weight?: number;
-}
-
-export default function A11ySkipTarget(props: Props) {
-  return (
-    <A11yContext.Consumer>
-      {({ addA11ySkipLink, removeA11ySkipLink }) => (
-        <A11ySkipTargetInner
-          addA11ySkipLink={addA11ySkipLink}
-          removeA11ySkipLink={removeA11ySkipLink}
-          {...props}
-        />
-      )}
-    </A11yContext.Consumer>
-  );
-}
-
-interface InnerProps {
-  addA11ySkipLink: (link: A11ySkipLink) => void;
-  removeA11ySkipLink: (link: A11ySkipLink) => void;
-}
-
-export class A11ySkipTargetInner extends React.PureComponent<Props & InnerProps> {
-  componentDidMount() {
-    this.props.addA11ySkipLink(this.getLink());
-  }
-
-  componentWillUnmount() {
-    this.props.removeA11ySkipLink(this.getLink());
-  }
-
-  getLink = (): A11ySkipLink => {
-    const { anchor: key, label = translate('skip_to_content'), weight } = this.props;
-    return { key, label, weight };
-  };
-
-  render() {
-    const { anchor } = this.props;
-    return <span id={`a11y_target__${anchor}`} />;
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/a11y/__tests__/A11yLinks-test.tsx b/server/sonar-web/src/main/js/components/a11y/__tests__/A11yLinks-test.tsx
deleted file mode 100644 (file)
index cbe0ff1..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import userEvent from '@testing-library/user-event';
-import * as React from 'react';
-import { renderComponent } from '../../../helpers/testReactTestingUtils';
-import { byRole } from '../../../helpers/testSelector';
-import { A11yContext } from '../A11yContext';
-import A11yProvider from '../A11yProvider';
-import A11ySkipLinks from '../A11ySkipLinks';
-
-const ui = {
-  links: byRole('link'),
-
-  // specific to test
-  addButton: byRole('button', { name: 'Add' }),
-  removeButton: byRole('button', { name: 'Remove' }),
-};
-
-it('should render correctly', async () => {
-  const user = userEvent.setup();
-
-  renderA11ySkipLinks();
-
-  expect(ui.links.queryAll()).toHaveLength(0);
-
-  await user.click(ui.addButton.get());
-  expect(ui.links.getAll()).toHaveLength(1);
-
-  await user.click(ui.addButton.get());
-  expect(ui.links.getAll()).toHaveLength(2);
-
-  await user.click(ui.removeButton.get());
-  expect(ui.links.getAll()).toHaveLength(1);
-});
-
-function renderA11ySkipLinks() {
-  return renderComponent(
-    <A11yProvider>
-      <A11ySkipLinks />
-
-      <LinkTester />
-    </A11yProvider>,
-    '/',
-    {},
-  );
-}
-
-let count = 0;
-
-function LinkTester() {
-  const { addA11ySkipLink, removeA11ySkipLink } = React.useContext(A11yContext);
-
-  return (
-    <>
-      <button
-        onClick={() => {
-          count += 1;
-          addA11ySkipLink({ key: `${count}`, label: `link #${count}` });
-        }}
-        type="button"
-      >
-        Add
-      </button>
-      <button
-        onClick={() => {
-          removeA11ySkipLink({ key: `${count}`, label: `link #${count}` });
-          count -= 1;
-        }}
-        type="button"
-      >
-        Remove
-      </button>
-    </>
-  );
-}
diff --git a/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11yContext.tsx b/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11yContext.tsx
new file mode 100644 (file)
index 0000000..ba6908b
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { createContext } from 'react';
+import { A11ySkipLink } from '../../../types/types';
+
+export interface A11yContextShape {
+  addA11ySkipLink: (link: A11ySkipLink) => void;
+  removeA11ySkipLink: (link: A11ySkipLink) => void;
+  links: A11ySkipLink[];
+}
+
+export const A11yContext = createContext<A11yContextShape>({
+  addA11ySkipLink: () => {
+    /* Implemented by Provider */
+  },
+  removeA11ySkipLink: () => {
+    /* Implemented by Provider */
+  },
+  links: [],
+});
diff --git a/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11yProvider.tsx b/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11yProvider.tsx
new file mode 100644 (file)
index 0000000..45ae19d
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { sortBy } from 'lodash';
+import * as React from 'react';
+import { A11ySkipLink } from '../../../types/types';
+import { A11yContext } from './A11yContext';
+
+interface State {
+  links: A11ySkipLink[];
+}
+
+export default class A11yProvider extends React.Component<React.PropsWithChildren, State> {
+  keys: string[] = [];
+  state: State = { links: [] };
+
+  addA11ySkipLink = (link: A11ySkipLink) => {
+    this.setState((prevState) => {
+      const links = [...prevState.links];
+      links.push({ ...link, weight: link.weight || 0 });
+      return { links };
+    });
+  };
+
+  removeA11ySkipLink = (link: A11ySkipLink) => {
+    this.setState((prevState) => {
+      const links = prevState.links.filter((l) => l.key !== link.key);
+      return { links };
+    });
+  };
+
+  render() {
+    const links = sortBy(this.state.links, 'weight');
+    return (
+      <A11yContext.Provider
+        value={{
+          addA11ySkipLink: this.addA11ySkipLink,
+          links,
+          removeA11ySkipLink: this.removeA11ySkipLink,
+        }}
+      >
+        {this.props.children}
+      </A11yContext.Provider>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipLinks.css b/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipLinks.css
new file mode 100644 (file)
index 0000000..75f5043
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+.a11y-skip-link {
+  width: 0;
+  height: 0;
+  padding: var(--gridSize);
+  position: absolute;
+  left: -9999px;
+  top: -9999px;
+  border: 0;
+  font-size: 1rem;
+  text-align: center;
+  z-index: 999;
+}
+
+.a11y-skip-link:focus {
+  width: auto;
+  height: auto;
+  left: 6px;
+  top: 6px;
+  color: white;
+  background-color: var(--globalNavBarBg);
+  text-decoration: underline;
+}
diff --git a/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipLinks.tsx b/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipLinks.tsx
new file mode 100644 (file)
index 0000000..c9fd17c
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { A11yContext } from './A11yContext';
+import './A11ySkipLinks.css';
+
+export default function A11ySkipLinks() {
+  return (
+    <A11yContext.Consumer>
+      {({ links }) =>
+        links.map((link) => (
+          <a className="a11y-skip-link" href={`#a11y_target__${link.key}`} key={link.key}>
+            {link.label}
+          </a>
+        ))
+      }
+    </A11yContext.Consumer>
+  );
+}
diff --git a/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipTarget.tsx b/server/sonar-web/src/main/js/sonar-aligned/components/a11y/A11ySkipTarget.tsx
new file mode 100644 (file)
index 0000000..0953982
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+import { A11ySkipLink } from '../../../types/types';
+import { A11yContext } from './A11yContext';
+
+interface Props {
+  anchor: string;
+  label?: string;
+  weight?: number;
+}
+
+export default function A11ySkipTarget(props: Props) {
+  return (
+    <A11yContext.Consumer>
+      {({ addA11ySkipLink, removeA11ySkipLink }) => (
+        <A11ySkipTargetInner
+          addA11ySkipLink={addA11ySkipLink}
+          removeA11ySkipLink={removeA11ySkipLink}
+          {...props}
+        />
+      )}
+    </A11yContext.Consumer>
+  );
+}
+
+interface InnerProps {
+  addA11ySkipLink: (link: A11ySkipLink) => void;
+  removeA11ySkipLink: (link: A11ySkipLink) => void;
+}
+
+export class A11ySkipTargetInner extends React.PureComponent<Props & InnerProps> {
+  componentDidMount() {
+    this.props.addA11ySkipLink(this.getLink());
+  }
+
+  componentWillUnmount() {
+    this.props.removeA11ySkipLink(this.getLink());
+  }
+
+  getLink = (): A11ySkipLink => {
+    const { anchor: key, label = translate('skip_to_content'), weight } = this.props;
+    return { key, label, weight };
+  };
+
+  render() {
+    const { anchor } = this.props;
+    return <span id={`a11y_target__${anchor}`} />;
+  }
+}
diff --git a/server/sonar-web/src/main/js/sonar-aligned/components/a11y/__tests__/A11yLinks-test.tsx b/server/sonar-web/src/main/js/sonar-aligned/components/a11y/__tests__/A11yLinks-test.tsx
new file mode 100644 (file)
index 0000000..571368c
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import userEvent from '@testing-library/user-event';
+import * as React from 'react';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { byRole } from '../../../../helpers/testSelector';
+import { A11yContext } from '../A11yContext';
+import A11yProvider from '../A11yProvider';
+import A11ySkipLinks from '../A11ySkipLinks';
+
+const ui = {
+  links: byRole('link'),
+
+  // specific to test
+  addButton: byRole('button', { name: 'Add' }),
+  removeButton: byRole('button', { name: 'Remove' }),
+};
+
+it('should render correctly', async () => {
+  const user = userEvent.setup();
+
+  renderA11ySkipLinks();
+
+  expect(ui.links.queryAll()).toHaveLength(0);
+
+  await user.click(ui.addButton.get());
+  expect(ui.links.getAll()).toHaveLength(1);
+
+  await user.click(ui.addButton.get());
+  expect(ui.links.getAll()).toHaveLength(2);
+
+  await user.click(ui.removeButton.get());
+  expect(ui.links.getAll()).toHaveLength(1);
+});
+
+function renderA11ySkipLinks() {
+  return renderComponent(
+    <A11yProvider>
+      <A11ySkipLinks />
+
+      <LinkTester />
+    </A11yProvider>,
+    '/',
+    {},
+  );
+}
+
+let count = 0;
+
+function LinkTester() {
+  const { addA11ySkipLink, removeA11ySkipLink } = React.useContext(A11yContext);
+
+  return (
+    <>
+      <button
+        onClick={() => {
+          count += 1;
+          addA11ySkipLink({ key: `${count}`, label: `link #${count}` });
+        }}
+        type="button"
+      >
+        Add
+      </button>
+      <button
+        onClick={() => {
+          removeA11ySkipLink({ key: `${count}`, label: `link #${count}` });
+          count -= 1;
+        }}
+        type="button"
+      >
+        Remove
+      </button>
+    </>
+  );
+}