diff options
author | David Cho-Lerat <david.cho-lerat@sonarsource.com> | 2024-03-15 18:24:42 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-03-18 20:02:30 +0000 |
commit | 03600808205c4f6df77713dbd40ee0a6e9c25a48 (patch) | |
tree | 8c90b9014c31a1715c5b59e8ef4eab1b53a422e7 | |
parent | 8c71dbf2097fc53ae83a2d54be5099f5cfbd76f4 (diff) | |
download | sonarqube-03600808205c4f6df77713dbd40ee0a6e9c25a48.tar.gz sonarqube-03600808205c4f6df77713dbd40ee0a6e9c25a48.zip |
SONAR-21867 Create Image component with baseUrl
24 files changed, 221 insertions, 166 deletions
diff --git a/server/sonar-web/.eslintrc b/server/sonar-web/.eslintrc index 3634bec594b..e249b4c013b 100644 --- a/server/sonar-web/.eslintrc +++ b/server/sonar-web/.eslintrc @@ -9,6 +9,17 @@ "rules": { "camelcase": "off", "promise/no-return-wrap": "warn", + "react/forbid-elements": [ + "warn", + { + "forbid": [ + { + "element": "img", + "message": "use <Image> from components/common instead" + } + ] + } + ], "react/jsx-curly-brace-presence": "warn", "testing-library/render-result-naming-convention": "off", /* Local rules, defined in ./eslint-local-rules/ */ diff --git a/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx b/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx index 4f8a19c4d40..51dd597b733 100644 --- a/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx +++ b/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx @@ -17,6 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { LinkStandalone } from '@sonarsource/echoes-react'; import { ButtonPrimary, Card, @@ -24,7 +26,6 @@ import { CheckIcon, ClipboardButton, InputField, - Link, ListItem, Note, OrderedList, @@ -33,6 +34,7 @@ import { import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { useSearchParams } from 'react-router-dom'; +import { Image } from '../../components/common/Image'; import { whenLoggedIn } from '../../components/hoc/whenLoggedIn'; import { translate, translateWithParameters } from '../../helpers/l10n'; import { generateSonarLintUserToken, portIsValid, sendUserToken } from '../../helpers/sonarlint'; @@ -88,10 +90,9 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { {status === Status.request && ( <> <Title>{translate('sonarlint-connection.request.title')}</Title> - <img - alt="" + <Image + alt="sonarlint-connection-request" className="sw-my-4" - role="presentation" src="/images/SonarLint-connection-request.png" /> <p className="sw-my-4"> @@ -107,7 +108,7 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { {status === Status.tokenError && ( <> - <img alt="" className="sw-my-4 sw-pt-2" role="presentation" src="/images/cross.svg" /> + <Image alt="sonarlint-token-error" className="sw-my-4 sw-pt-2" src="/images/cross.svg" /> <Title>{translate('sonarlint-connection.token-error.title')}</Title> <p className="sw-my-4">{translate('sonarlint-connection.token-error.description')}</p> <p className="sw-mb-4"> @@ -116,9 +117,9 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { defaultMessage={translate('sonarlint-connection.token-error.description2')} values={{ link: ( - <Link to="/account/security"> + <LinkStandalone to="/account/security"> {translate('sonarlint-connection.token-error.description2.link')} - </Link> + </LinkStandalone> ), }} /> @@ -128,7 +129,11 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { {status === Status.tokenCreated && newToken && ( <> - <img alt="" className="sw-my-4 sw-pt-2" role="presentation" src="/images/check.svg" /> + <Image + alt="sonarlint-connection-error" + className="sw-my-4 sw-pt-2" + src="/images/check.svg" + /> <Title>{translate('sonarlint-connection.connection-error.title')}</Title> <p className="sw-my-6"> {translate('sonarlint-connection.connection-error.description')} @@ -160,10 +165,9 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { {status === Status.tokenSent && newToken && ( <> <Title>{translate('sonarlint-connection.success.title')}</Title> - <img - alt="" + <Image + alt="sonarlint-connection-success" className="sw-mb-4" - role="presentation" src="/images/SonarLint-connection-ok.png" /> <p className="sw-my-4"> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx index 4e8bbb63971..c94295f147b 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx @@ -20,9 +20,9 @@ import { LinkStandalone } from '@sonarsource/echoes-react'; import React from 'react'; +import { Image } from '../../../../../components/common/Image'; import { isPullRequest } from '../../../../../helpers/branch-like'; import { translate, translateWithParameters } from '../../../../../helpers/l10n'; -import { getBaseUrl } from '../../../../../helpers/system'; import { isDefined } from '../../../../../helpers/types'; import { AlmKeys } from '../../../../../types/alm-settings'; import { BranchLike } from '../../../../../types/branch-like'; @@ -70,10 +70,10 @@ export default function PRLink({ <LinkStandalone iconLeft={ almKey !== '' && ( - <img + <Image alt={almKey} height={16} - src={`${getBaseUrl()}/images/alm/${almKey}.svg`} + src={`/images/alm/${almKey}.svg`} title={translateWithParameters('branches.see_the_pr_on_x', translate(almKey))} /> ) diff --git a/server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx b/server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx index 6c14bb0f749..05e799562df 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx @@ -17,8 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { MainAppBar, SonarQubeLogo } from 'design-system'; import * as React from 'react'; +import { Image } from '../../../../components/common/Image'; import { translate } from '../../../../helpers/l10n'; import { GlobalSettingKeys } from '../../../../types/settings'; import { AppStateContext } from '../../app-state/AppStateContext'; @@ -35,7 +37,7 @@ function LogoWithAriaText() { return ( <div aria-label={title} role="img"> {customLogoUrl ? ( - <img alt={title} src={customLogoUrl} width={customLogoWidth} /> + <Image alt={title} src={customLogoUrl} width={customLogoWidth} /> ) : ( <SonarQubeLogo /> )} diff --git a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx index b47b0ebf588..0256393b450 100644 --- a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx @@ -17,12 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import styled from '@emotion/styled'; import { ButtonPrimary, ButtonSecondary, themeBorder, themeColor } from 'design-system'; import * as React from 'react'; import { dismissNotice } from '../../../api/users'; +import { Image } from '../../../components/common/Image'; import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; import { NoticeType, isLoggedIn } from '../../../types/users'; import { CurrentUserContextInterface } from '../current-user/CurrentUserContext'; import withCurrentUserContext from '../current-user/withCurrentUserContext'; @@ -47,7 +48,7 @@ export function PromotionNotification(props: CurrentUserContextInterface) { return ( <PromotionNotificationWrapper className="it__promotion_notification sw-z-global-popup sw-rounded-1 sw-flex sw-items-center sw-px-4"> <div className="sw-mr-2"> - <img alt="SonarQube + SonarLint" height={80} src={`${getBaseUrl()}/images/sq-sl.svg`} /> + <Image alt="SonarQube + SonarLint" height={80} src="/images/sq-sl.svg" /> </div> <PromotionNotificationContent className="sw-flex-1 sw-px-2 sw-py-4"> <span className="sw-body-sm-highlight">{translate('promotion.sonarlint.title')}</span> diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx index 7c317553268..058bad865d5 100644 --- a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx +++ b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx @@ -17,11 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { getTextColor } from 'design-system'; import * as React from 'react'; import { getIdentityProviders } from '../../../api/users'; import { colors } from '../../../app/theme'; -import { getBaseUrl } from '../../../helpers/system'; +import { Image } from '../../../components/common/Image'; import { IdentityProvider } from '../../../types/types'; import { LoggedInUser } from '../../../types/users'; @@ -104,11 +105,11 @@ export default class UserExternalIdentity extends React.PureComponent< color: getTextColor(identityProvider.backgroundColor, colors.secondFontColor), }} > - <img + <Image alt={identityProvider.name} className="sw-mr-1" height="14" - src={getBaseUrl() + identityProvider.iconPath} + src={identityProvider.iconPath} width="14" /> {user.externalIdentity} diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx index 07e97291e93..5c91aa611a7 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx @@ -17,22 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + /* eslint-disable react/no-unused-prop-types */ + +import { LinkStandalone, Spinner } from '@sonarsource/echoes-react'; import { ButtonSecondary, GreyCard, HelperHintIcon, LightPrimary, - Spinner, - StandoutLink, TextMuted, Title, } from 'design-system'; import * as React from 'react'; import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; +import { Image } from '../../../components/common/Image'; import HelpTooltip from '../../../components/controls/HelpTooltip'; import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; import { getCreateProjectModeLocation } from '../../../helpers/urls'; import { AlmKeys } from '../../../types/alm-settings'; import { AppState } from '../../../types/appstate'; @@ -79,12 +80,10 @@ function renderAlmOption( const svgFileNameGrey = `${svgFileName}_grey`; const icon = ( - <img + <Image alt="" // Should be ignored by screen readers className="sw-h-4 sw-w-4" - src={`${getBaseUrl()}/images/alm/${ - !disabled && hasConfig ? svgFileName : svgFileNameGrey - }.svg`} + src={`/images/alm/${!disabled && hasConfig ? svgFileName : svgFileNameGrey}.svg`} /> ); @@ -92,9 +91,11 @@ function renderAlmOption( <GreyCard key={alm} className="sw-col-span-4 sw-p-4 sw-flex sw-justify-between sw-items-center"> <div className="sw-items-center sw-flex sw-py-2"> {!disabled && hasConfig ? ( - <StandoutLink icon={icon} to={getCreateProjectModeLocation(mode)}> - {translate('onboarding.create_project.import_select_method', alm)} - </StandoutLink> + <LinkStandalone iconLeft={icon} to={getCreateProjectModeLocation(mode)}> + <span className="sw-ml-2"> + {translate('onboarding.create_project.import_select_method', alm)} + </span> + </LinkStandalone> ) : ( <> {icon} @@ -106,7 +107,7 @@ function renderAlmOption( )} </div> - <Spinner loading={loadingBindings}> + <Spinner isLoading={loadingBindings}> {!hasConfig && (canAdmin ? ( <ButtonSecondary onClick={() => props.onConfigMode(configMode)}> @@ -167,9 +168,9 @@ export function CreateProjectModeSelection(props: CreateProjectModeSelectionProp <div className="sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12"> <GreyCard className="sw-col-span-4 sw-p-4 sw-py-6 sw-flex sw-justify-between sw-items-center"> <div> - <StandoutLink to={getCreateProjectModeLocation(CreateProjectModes.Manual)}> + <LinkStandalone to={getCreateProjectModeLocation(CreateProjectModes.Manual)}> {translate('onboarding.create_project.import_select_method.manual')} - </StandoutLink> + </LinkStandalone> </div> </GreyCard> </div> diff --git a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx index a9c4957b5ea..9ddd4b52803 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { ActionsDropdown, Badge, @@ -33,8 +34,8 @@ import { } from 'design-system'; import * as React from 'react'; import { useState } from 'react'; +import { Image } from '../../../components/common/Image'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; import { useGroupMembersCountQuery } from '../../../queries/group-memberships'; import { Group, Provider } from '../../../types/types'; import DeleteGroupForm from './DeleteGroupForm'; @@ -69,11 +70,11 @@ export default function ListItem(props: Readonly<ListItemProps>) { } return ( - <img + <Image alt={identityProvider} className="sw-ml-2 sw-mr-2" height={16} - src={`${getBaseUrl()}/images/alm/${identityProvider}.svg`} + src={`/images/alm/${identityProvider}.svg`} /> ); }; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx index f845f0fd429..a507e9e1241 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx @@ -17,9 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { SubHeading, UnorderedList } from 'design-system'; import * as React from 'react'; -import { getBaseUrl } from '../../../helpers/system'; +import { Image } from '../../../components/common/Image'; import { Edition, EditionKey } from '../../../types/editions'; interface Props { @@ -32,11 +33,11 @@ export default function EditionBox({ edition }: Readonly<Props>) { return ( <div> <SubHeading as="h2" id="data-center-edition"> - <img + <Image alt="SonarQube logo" className="sw-mr-2" width={16} - src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`} + src="/images/embed-doc/sq-icon.svg" /> <span>Data Center Edition</span> </SubHeading> @@ -56,11 +57,11 @@ export default function EditionBox({ edition }: Readonly<Props>) { return ( <div> <SubHeading as="h2" id="enterprise-edition"> - <img + <Image alt="SonarQube logo" className="sw-mr-2" width={16} - src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`} + src="/images/embed-doc/sq-icon.svg" /> <span>Enterprise Edition</span> </SubHeading> @@ -83,11 +84,11 @@ export default function EditionBox({ edition }: Readonly<Props>) { return ( <div> <SubHeading as="h2" id="developer-edition"> - <img + <Image alt="SonarQube logo" className="sw-mr-2" width={16} - src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`} + src="/images/embed-doc/sq-icon.svg" /> <span>Developer Edition</span> </SubHeading> @@ -98,28 +99,18 @@ export default function EditionBox({ edition }: Readonly<Props>) { <UnorderedList className="sw-ml-8" ticks> <li> <span>PR / MR decoration & Quality Gate</span> - <img - alt="GitHub" - className="sw-ml-2" - src={`${getBaseUrl()}/images/alm/github.svg`} - width={16} - /> - <img - alt="GitLab" - className="sw-ml-2" - src={`${getBaseUrl()}/images/alm/gitlab.svg`} - width={16} - /> - <img + <Image alt="GitHub" className="sw-ml-2" src="/images/alm/github.svg" width={16} /> + <Image alt="GitLab" className="sw-ml-2" src="/images/alm/gitlab.svg" width={16} /> + <Image alt="Azure DevOps" className="sw-ml-2" - src={`${getBaseUrl()}/images/alm/azure.svg`} + src="/images/alm/azure.svg" width={16} /> - <img + <Image alt="Bitbucket" className="sw-ml-2" - src={`${getBaseUrl()}/images/alm/bitbucket.svg`} + src="/images/alm/bitbucket.svg" width={16} /> </li> diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx index cc50083316b..104eaf2ec42 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx @@ -17,13 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Link, Note, getTabPanelId } from 'design-system'; + +import { Link } from '@sonarsource/echoes-react'; +import { Note, getTabPanelId } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import DocumentationLink from '../../../components/common/DocumentationLink'; +import { Image } from '../../../components/common/Image'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; import { CodeScope, queryToSearch } from '../../../helpers/urls'; import { Branch } from '../../../types/branch-like'; import { ComponentQualifier } from '../../../types/component'; @@ -42,7 +44,10 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp const isApp = component.qualifier === ComponentQualifier.Application; const hasBadReferenceBranch = - !isApp && !!period && !period.date && period.mode === NewCodeDefinitionType.ReferenceBranch; + !isApp && + !!period && + period.date === '' && + period.mode === NewCodeDefinitionType.ReferenceBranch; /* * If the period is "reference branch"-based, and if there's no date, it means * that we're not lacking a second analysis, but that we'll never have new code because the @@ -65,11 +70,11 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp id={getTabPanelId(CodeScope.New)} style={{ height: 500 }} > - <img + <Image alt="" /* Make screen readers ignore this image; it's purely eye candy. */ className="sw-mr-2" height={52} - src={`${getBaseUrl()}/images/source-code.svg`} + src="/images/source-code.svg" /> <Note as="div" className="sw-ml-4 sw-max-w-abs-500"> <p className="sw-mb-2 sw-mt-4">{translate(badExplanationKey)}</p> diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx index 622eb382e04..08a16d37679 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx @@ -17,11 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { ButtonPrimary, FlagMessage, Title } from 'design-system'; import * as React from 'react'; import GitHubSynchronisationWarning from '../../../../app/components/GitHubSynchronisationWarning'; +import { Image } from '../../../../components/common/Image'; import { translate } from '../../../../helpers/l10n'; -import { getBaseUrl } from '../../../../helpers/system'; +import { isDefined } from '../../../../helpers/types'; import { useGithubProvisioningEnabledQuery } from '../../../../queries/identity-provider/github'; import { isApplication, isPortfolioLike, isProject } from '../../../../types/component'; import { Component } from '../../../../types/types'; @@ -69,19 +71,19 @@ export default function PageHeader(props: Props) { <Title> {translate('permissions.page')} {provisionedByGitHub && ( - <img + <Image alt="github" className="sw-mx-2 sw-align-baseline" aria-label={translate('project_permission.github_managed')} height={16} - src={`${getBaseUrl()}/images/alm/github.svg`} + src="/images/alm/github.svg" /> )} </Title> <div> <p>{description}</p> - {visibilityDescription && <p>{visibilityDescription}</p>} + {isDefined(visibilityDescription) && <p>{visibilityDescription}</p>} {provisionedByGitHub && ( <> <p>{translate('roles.page.description.github')}</p> diff --git a/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx b/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx index 57ca2e0521e..d3ae3930556 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { Spinner } from '@sonarsource/echoes-react'; import { BasicSeparator, @@ -32,6 +33,7 @@ import { import { isEmpty } from 'lodash'; import * as React from 'react'; import { useState } from 'react'; +import { Image } from '../../../components/common/Image'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { localizeMetric } from '../../../helpers/measures'; @@ -103,7 +105,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { onClick={() => handleSelectType(BadgeType.measure)} selected={BadgeType.measure === selectedType} image={ - <img + <Image alt={translate('overview.badges', BadgeType.measure, 'alt')} src={getBadgeUrl(BadgeType.measure, fullBadgeOptions, token)} /> @@ -115,7 +117,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { onClick={() => handleSelectType(BadgeType.qualityGate)} selected={BadgeType.qualityGate === selectedType} image={ - <img + <Image alt={translate('overview.badges', BadgeType.qualityGate, 'alt')} src={getBadgeUrl(BadgeType.qualityGate, fullBadgeOptions, token)} width="128px" diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx index 5d7c9e57d97..85e45472453 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { ItemLink } from 'design-system'; import * as React from 'react'; +import { Image } from '../../../components/common/Image'; import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; import { queryToSearch } from '../../../helpers/urls'; import { AlmKeys } from '../../../types/alm-settings'; @@ -40,12 +41,7 @@ export default function ProjectCreationMenuItem(props: ProjectCreationMenuItemPr to={{ pathname: '/projects/create', search: queryToSearch({ mode: alm }) }} > {alm !== 'manual' && ( - <img - alt={alm} - className="sw-mr-2" - width={16} - src={`${getBaseUrl()}/images/alm/${almIcon}.svg`} - /> + <Image alt={alm} className="sw-mr-2" width={16} src={`/images/alm/${almIcon}.svg`} /> )} {translate('my_account.add_project', alm)} </ItemLink> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx index 6e21825714a..8a989fae4b1 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx @@ -17,11 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { Note } from 'design-system'; import * as React from 'react'; import DocumentationLink from '../../../components/common/DocumentationLink'; +import { Image } from '../../../components/common/Image'; import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; export interface EmptyHotspotsPageProps { filtered: boolean; @@ -45,13 +46,11 @@ export default function EmptyHotspotsPage(props: EmptyHotspotsPageProps) { return ( <div className="sw-items-center sw-justify-center sw-flex-col sw-flex sw-pt-16"> - <img + <Image alt={translate('hotspots.page')} className="sw-mt-8" height={100} - src={`${getBaseUrl()}/images/${ - filtered && !filterByFile ? 'filter-large' : 'hotspot-large' - }.svg`} + src={`/images/${filtered && !filterByFile ? 'filter-large' : 'hotspot-large'}.svg`} /> <h1 className="sw-mt-10 sw-body-sm-highlight"> {translate(`hotspots.${translationRoot}.title`)} diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx b/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx index 611756f59ce..ab22b747608 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx @@ -17,22 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import styled from '@emotion/styled'; +import { Spinner } from '@sonarsource/echoes-react'; import { Card, FlagMessage, PageContentFontWrapper, - Spinner, Title, themeBorder, themeColor, } from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; +import { Image } from '../../../components/common/Image'; import { Location } from '../../../components/hoc/withRouter'; import { translate } from '../../../helpers/l10n'; import { sanitizeUserInput } from '../../../helpers/sanitize'; -import { getBaseUrl } from '../../../helpers/system'; import { getReturnUrl } from '../../../helpers/urls'; import { IdentityProvider } from '../../../types/types'; import LoginForm from './LoginForm'; @@ -54,17 +55,12 @@ export default function Login(props: Readonly<LoginProps>) { return ( <div className="sw-flex sw-flex-col sw-items-center" id="login_form"> <Helmet defer={false} title={translate('login.page')} /> - <img alt="" className="sw-mt-32" src={`${getBaseUrl()}/images/sonar-logo-horizontal.png`} /> + <Image alt="" className="sw-mt-32" src="/images/sonar-logo-horizontal.png" /> <Card className="sw-my-14 sw-p-0 sw-w-abs-350"> <PageContentFontWrapper className="sw-body-md sw-flex sw-flex-col sw-items-center sw-py-8 sw-px-4"> - <img - alt="" - className="sw-mb-6" - src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`} - width={28} - /> + <Image alt="" className="sw-mb-6" src="/images/embed-doc/sq-icon.svg" width={28} /> <Title className="sw-mb-6">{translate('login.login_to_sonarqube')}</Title> - <Spinner loading={loading}> + <Spinner isLoading={loading}> <> {displayError && ( <FlagMessage className="sw-mb-6" variant="error"> diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx index a0b21bed4f4..73da55bf78f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx @@ -17,11 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { FlagMessage, Link, SubTitle, ToggleButton } from 'design-system'; + +import { Link } from '@sonarsource/echoes-react'; +import { FlagMessage, SubTitle, ToggleButton } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import { Image } from '../../../../components/common/Image'; import { translate } from '../../../../helpers/l10n'; -import { getBaseUrl } from '../../../../helpers/system'; +import { isDefined } from '../../../../helpers/types'; import { useGetValuesQuery } from '../../../../queries/settings'; import { AlmKeys, @@ -56,12 +59,7 @@ const tabs = [ { label: ( <> - <img - alt="github" - className="sw-mr-2" - height={16} - src={`${getBaseUrl()}/images/alm/github.svg`} - /> + <Image alt="github" className="sw-mr-2" height={16} src="/images/alm/github.svg" /> {translate('settings.almintegration.tab.github')} </> ), @@ -70,12 +68,7 @@ const tabs = [ { label: ( <> - <img - alt="bitbucket" - className="sw-mr-2" - height={16} - src={`${getBaseUrl()}/images/alm/bitbucket.svg`} - /> + <Image alt="bitbucket" className="sw-mr-2" height={16} src="/images/alm/bitbucket.svg" /> {translate('settings.almintegration.tab.bitbucket')} </> ), @@ -84,12 +77,7 @@ const tabs = [ { label: ( <> - <img - alt="azure" - className="sw-mr-2" - height={16} - src={`${getBaseUrl()}/images/alm/azure.svg`} - /> + <Image alt="azure" className="sw-mr-2" height={16} src="/images/alm/azure.svg" /> {translate('settings.almintegration.tab.azure')} </> ), @@ -98,12 +86,7 @@ const tabs = [ { label: ( <> - <img - alt="gitlab" - className="sw-mr-2" - height={16} - src={`${getBaseUrl()}/images/alm/gitlab.svg`} - /> + <Image alt="gitlab" className="sw-mr-2" height={16} src="/images/alm/gitlab.svg" /> {translate('settings.almintegration.tab.gitlab')} </> ), @@ -182,7 +165,7 @@ export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProp onUpdateDefinitions={props.onUpdateDefinitions} /> - {definitionKeyForDeletion && ( + {isDefined(definitionKeyForDeletion) && ( <DeleteModal id={definitionKeyForDeletion} onCancel={props.onCancelDelete} diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx index a3eac561c30..5358118d64a 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx @@ -17,16 +17,18 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { Link } from '@sonarsource/echoes-react'; import classNames from 'classnames'; -import { FlagMessage, Link, SubTitle, ToggleButton, getTabId, getTabPanelId } from 'design-system'; +import { FlagMessage, SubTitle, ToggleButton, getTabId, getTabPanelId } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { useSearchParams } from 'react-router-dom'; import withAvailableFeatures, { WithAvailableFeaturesProps, } from '../../../../app/components/available-features/withAvailableFeatures'; +import { Image } from '../../../../components/common/Image'; import { translate } from '../../../../helpers/l10n'; -import { getBaseUrl } from '../../../../helpers/system'; import { searchParamsToQuery } from '../../../../helpers/urls'; import { AlmKeys } from '../../../../types/alm-settings'; import { Feature } from '../../../../types/features'; @@ -54,9 +56,7 @@ export const DOCUMENTATION_LINK_SUFFIXES = { }; function renderDevOpsIcon(key: string) { - return ( - <img alt={key} className="sw-mr-2" height={16} src={`${getBaseUrl()}/images/alm/${key}.svg`} /> - ); + return <Image alt={key} className="sw-mr-2" height={16} src={`/images/alm/${key}.svg`} />; } export function Authentication(props: Props & WithAvailableFeaturesProps) { diff --git a/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx b/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx index ad93135f5aa..ce64e3bae55 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx @@ -21,8 +21,9 @@ import { Badge, Note, getTextColor } from 'design-system'; import * as React from 'react'; import { colors } from '../../../app/theme'; +import { Image } from '../../../components/common/Image'; import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; +import { isDefined } from '../../../helpers/types'; import { IdentityProvider, Provider } from '../../../types/types'; import { RestUserDetailed } from '../../../types/users'; @@ -39,7 +40,9 @@ export default function UserListItemIdentity({ identityProvider, user, managePro <strong className="it__user-name sw-body-sm-highlight">{user.name}</strong> <Note className="it__user-login">{user.login}</Note> </div> - {user.email && <div className="it__user-email sw-mt-1">{user.email}</div>} + {isDefined(user.email) && user.email !== '' && ( + <div className="it__user-email sw-mt-1">{user.email}</div> + )} {!user.local && user.externalProvider !== 'sonarqube' && ( <ExternalProvider identityProvider={identityProvider} user={user} /> )} @@ -68,11 +71,11 @@ export function ExternalProvider({ identityProvider, user }: Omit<Props, 'manage color: getTextColor(identityProvider.backgroundColor, colors.secondFontColor), }} > - <img + <Image alt={identityProvider.name} className="sw-mr-1" height="14" - src={getBaseUrl() + identityProvider.iconPath} + src={identityProvider.iconPath} width="14" /> {user.externalLogin} diff --git a/server/sonar-web/src/main/js/components/common/Image.tsx b/server/sonar-web/src/main/js/components/common/Image.tsx new file mode 100644 index 00000000000..e15f8df3fcc --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/Image.tsx @@ -0,0 +1,42 @@ +/* + * 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 { getBaseUrl } from '../../helpers/system'; + +export function Image(props: Readonly<JSX.IntrinsicElements['img']>) { + const { alt, src: source, ...rest } = props; + + const baseUrl = getBaseUrl(); + + let src = source; + + if ( + src !== undefined && + !src.startsWith(baseUrl) && + !src.startsWith('http') && + !src.startsWith('data:') + ) { + src = `${baseUrl}/${src}`.replace(/(?<!:)\/+/g, '/'); + } + + // eslint-disable-next-line react/forbid-elements + return <img alt={alt} src={src} {...rest} />; +} diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx index 3555367e6b6..8f613d698e5 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx @@ -17,11 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { ItemDivider, ItemHeader, ItemLink, OpenNewTabIcon } from 'design-system'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; -import { getBaseUrl } from '../../helpers/system'; import { SuggestionLink } from '../../types/types'; +import { Image } from '../common/Image'; import { DocItemLink } from './DocItemLink'; import { SuggestionsContext } from './SuggestionsContext'; @@ -36,12 +37,12 @@ function IconLink({ }) { return ( <ItemLink to={link}> - <img + <Image alt={text} aria-hidden className="sw-mr-2" height="18" - src={`${getBaseUrl()}/images/${icon}`} + src={`/images/${icon}`} width="18" /> {text} diff --git a/server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx b/server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx index a1a7d38d5ad..fb2d9b3e382 100644 --- a/server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx +++ b/server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx @@ -17,13 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { Badge, ContentCell, TableRowInteractive, UserGroupIcon } from 'design-system'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { isPermissionDefinitionGroup } from '../../helpers/permissions'; -import { getBaseUrl } from '../../helpers/system'; +import { isDefined } from '../../helpers/types'; import { Permissions } from '../../types/permissions'; import { PermissionDefinitions, PermissionGroup } from '../../types/types'; +import { Image } from '../common/Image'; import PermissionCell from './PermissionCell'; import usePermissionChange from './usePermissionChange'; @@ -63,12 +65,12 @@ export default function GroupHolder(props: Props) { <strong>{group.name}</strong> </div> {disabled && ( - <img + <Image alt="github" className="sw-ml-2" aria-label={translate('project_permission.github_managed')} height={16} - src={`${getBaseUrl()}/images/alm/github.svg`} + src="/images/alm/github.svg" /> )} {group.name === ANYONE && ( @@ -77,7 +79,9 @@ export default function GroupHolder(props: Props) { </Badge> )} </div> - {description && <div className="sw-mt-2 sw-whitespace-normal">{description}</div>} + {isDefined(description) && ( + <div className="sw-mt-2 sw-whitespace-normal">{description}</div> + )} </div> </div> </ContentCell> diff --git a/server/sonar-web/src/main/js/components/permissions/UserHolder.tsx b/server/sonar-web/src/main/js/components/permissions/UserHolder.tsx index e07c01b6f87..de523fdd992 100644 --- a/server/sonar-web/src/main/js/components/permissions/UserHolder.tsx +++ b/server/sonar-web/src/main/js/components/permissions/UserHolder.tsx @@ -17,12 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { Avatar, ContentCell, Note, TableRowInteractive } from 'design-system'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { isPermissionDefinitionGroup } from '../../helpers/permissions'; -import { getBaseUrl } from '../../helpers/system'; +import { isDefined } from '../../helpers/types'; import { PermissionDefinitions, PermissionUser } from '../../types/types'; +import { Image } from '../common/Image'; import PermissionCell from './PermissionCell'; import usePermissionChange from './usePermissionChange'; @@ -90,16 +92,16 @@ export default function UserHolder(props: Props) { <Note className="sw-ml-2">{user.login}</Note> </div> {disabled && ( - <img + <Image alt="github" className="sw-ml-2" height={16} aria-label={translate('project_permission.github_managed')} - src={`${getBaseUrl()}/images/alm/github.svg`} + src="/images/alm/github.svg" /> )} </div> - {user.email && ( + {isDefined(user.email) && ( <div className="sw-mt-2 sw-max-w-100 sw-text-ellipsis sw-whitespace-nowrap sw-overflow-hidden"> {user.email} </div> diff --git a/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx b/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx index 35dd791d8f4..9a5328edeaa 100644 --- a/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx @@ -18,15 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { LinkHighlight, LinkStandalone, Spinner } from '@sonarsource/echoes-react'; import { Breadcrumbs, FlagMessage, GreyCard, - HoverLink, LightLabel, LightPrimary, - Spinner, - StandoutLink, SubTitle, Title, } from 'design-system'; @@ -34,13 +32,13 @@ import * as React from 'react'; import { AnalysisStatus } from '../../apps/overview/components/AnalysisStatus'; import { isMainBranch } from '../../helpers/branch-like'; import { translate } from '../../helpers/l10n'; -import { getBaseUrl } from '../../helpers/system'; import { getProjectTutorialLocation } from '../../helpers/urls'; import { useBranchesQuery } from '../../queries/branch'; import { AlmKeys, AlmSettingsInstance, ProjectAlmBindingResponse } from '../../types/alm-settings'; import { MainBranch } from '../../types/branch-like'; import { Component } from '../../types/types'; import { LoggedInUser } from '../../types/users'; +import { Image } from '../common/Image'; import AzurePipelinesTutorial from './azure-pipelines/AzurePipelinesTutorial'; import BitbucketPipelinesTutorial from './bitbucket-pipelines/BitbucketPipelinesTutorial'; import GitHubActionTutorial from './github-action/GitHubActionTutorial'; @@ -66,9 +64,11 @@ export interface TutorialSelectionRendererProps { function renderAlm(mode: TutorialModes, project: string, icon?: React.ReactNode) { return ( <GreyCard className="sw-col-span-4 sw-p-4"> - <StandoutLink icon={icon} to={getProjectTutorialLocation(project, mode)}> - {translate('onboarding.tutorial.choose_method', mode)} - </StandoutLink> + <LinkStandalone iconLeft={icon} to={getProjectTutorialLocation(project, mode)}> + <span className={icon ? 'sw-ml-2' : ''}> + {translate('onboarding.tutorial.choose_method', mode)} + </span> + </LinkStandalone> {mode === TutorialModes.Local && ( <LightLabel as="p" className="sw-mt-3"> @@ -157,10 +157,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender renderAlm( TutorialModes.Jenkins, component.key, - <img + <Image alt="" // Should be ignored by screen readers className="sw-h-4 sw-w-4" - src={`${getBaseUrl()}/images/tutorials/jenkins.svg`} + src="/images/tutorials/jenkins.svg" />, )} @@ -168,10 +168,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender renderAlm( TutorialModes.GitHubActions, component.key, - <img + <Image alt="" // Should be ignored by screen readers className="sw-h-4 sw-w-4" - src={`${getBaseUrl()}/images/tutorials/github-actions.svg`} + src="/images/tutorials/github-actions.svg" />, )} @@ -179,10 +179,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender renderAlm( TutorialModes.BitbucketPipelines, component.key, - <img + <Image alt="" // Should be ignored by screen readers className="sw-h-4 sw-w-4" - src={`${getBaseUrl()}/images/alm/bitbucket.svg`} + src="/images/alm/bitbucket.svg" />, )} @@ -190,10 +190,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender renderAlm( TutorialModes.GitLabCI, component.key, - <img + <Image alt="" // Should be ignored by screen readers className="sw-h-4 sw-w-4" - src={`${getBaseUrl()}/images/alm/gitlab.svg`} + src="/images/alm/gitlab.svg" />, )} @@ -201,10 +201,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender renderAlm( TutorialModes.AzurePipelines, component.key, - <img + <Image alt="" // Should be ignored by screen readers className="sw-h-4 sw-w-4" - src={`${getBaseUrl()}/images/tutorials/azure-pipelines.svg`} + src="/images/tutorials/azure-pipelines.svg" />, )} @@ -217,13 +217,19 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender {selectedTutorial && ( <Breadcrumbs className="sw-mb-3"> - <HoverLink to={getProjectTutorialLocation(component.key)}> + <LinkStandalone + highlight={LinkHighlight.CurrentColor} + to={getProjectTutorialLocation(component.key)} + > {translate('onboarding.tutorial.breadcrumbs.home')} - </HoverLink> + </LinkStandalone> - <HoverLink to={getProjectTutorialLocation(component.key, selectedTutorial)}> + <LinkStandalone + highlight={LinkHighlight.CurrentColor} + to={getProjectTutorialLocation(component.key, selectedTutorial)} + > {translate('onboarding.tutorial.breadcrumbs', selectedTutorial)} - </HoverLink> + </LinkStandalone> </Breadcrumbs> )} diff --git a/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx b/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx index 24c5cf72802..a22c87b5e66 100644 --- a/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx @@ -17,11 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { LinkStandalone } from '@sonarsource/echoes-react'; import classNames from 'classnames'; -import { Card, LightLabel, StandoutLink } from 'design-system'; +import { Card, LightLabel } from 'design-system'; import React from 'react'; import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; +import { Image } from '../../common/Image'; import { OSs, TutorialModes } from '../types'; export interface GithubCFamilyExampleRepositoriesProps { @@ -58,15 +60,15 @@ export default function GithubCFamilyExampleRepositories( return ( <Card className={classNames('sw-p-4 sw-bg-inherit', className)}> <div> - <img + <Image alt="" // Should be ignored by screen readers className="sw-mr-2" height={20} - src={`${getBaseUrl()}/images/alm/github.svg`} + src="/images/alm/github.svg" /> - <StandoutLink target="_blank" to={link}> + <LinkStandalone target="_blank" to={link}> sonarsource-cfamily-examples - </StandoutLink> + </LinkStandalone> </div> <LightLabel as="p" className="sw-mt-4"> {translate('onboarding.tutorial.cfamily.examples_repositories_description')} |