From 94747daf9743ed21903f14b045fb01f2798d4507 Mon Sep 17 00:00:00 2001 From: stanislavh Date: Tue, 26 Nov 2024 15:06:59 +0100 Subject: SONAR-22310 Fix a11y issue on project information page --- .../projectInformation/ProjectInformationApp.tsx | 13 ++-- .../__tests__/ProjectInformationApp-it.tsx | 29 ++++---- .../apps/projectInformation/about/AboutProject.tsx | 27 +++---- .../about/components/MetaDescription.tsx | 11 ++- .../about/components/MetaKey.tsx | 50 ++++++++----- .../about/components/MetaLinks.tsx | 6 +- .../about/components/MetaQualityGate.tsx | 34 ++++++--- .../about/components/MetaQualityProfiles.tsx | 82 +++++++++++----------- .../about/components/MetaSize.tsx | 29 +++++--- .../about/components/MetaTags.tsx | 8 ++- .../about/components/MetaVisibility.tsx | 6 +- .../__tests__/MetaQualityProfiles-test.tsx | 8 ++- .../projectInformation/badges/ProjectBadges.tsx | 12 +++- .../badges/__tests__/ProjectBadges-it.tsx | 8 ++- .../js/design-system/components/CodeSnippet.tsx | 22 ++++-- .../components/IlllustredSelectionCard.tsx | 11 ++- .../main/js/design-system/components/clipboard.tsx | 3 + 17 files changed, 220 insertions(+), 139 deletions(-) (limited to 'server/sonar-web/src/main/js') diff --git a/server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx b/server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx index bcc3d3d4268..e808094109a 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx @@ -18,8 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Heading } from '@sonarsource/echoes-react'; import { useEffect, useState } from 'react'; -import { Card, LargeCenteredLayout, PageContentFontWrapper, Title } from '~design-system'; +import { Helmet } from 'react-helmet-async'; +import { Card, LargeCenteredLayout, PageContentFontWrapper } from '~design-system'; import { MetricKey } from '~sonar-aligned/types/metrics'; import { getMeasures } from '../../api/measures'; import withAvailableFeatures, { @@ -71,14 +73,17 @@ function ProjectInformationApp(props: Props) { const regulatoryReportFeatureEnabled = props.hasFeature(Feature.RegulatoryReport); const isApp = isApplication(component.qualifier); + const title = translate(isApp ? 'application' : 'project', 'info.title'); + return (
+
- - {translate(isApp ? 'application' : 'project', 'info.title')} - + + {title} +
diff --git a/server/sonar-web/src/main/js/apps/projectInformation/__tests__/ProjectInformationApp-it.tsx b/server/sonar-web/src/main/js/apps/projectInformation/__tests__/ProjectInformationApp-it.tsx index 867f9f660ba..9996a39d69b 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/__tests__/ProjectInformationApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/__tests__/ProjectInformationApp-it.tsx @@ -62,10 +62,9 @@ const modeHandler = new ModeServiceMock(); const ui = { projectPageTitle: byRole('heading', { name: 'project.info.title' }), applicationPageTitle: byRole('heading', { name: 'application.info.title' }), - qualityGateList: byRole('list', { name: 'project.info.quality_gate' }), - qualityProfilesList: byRole('list', { name: 'overview.quality_profiles' }), - externalLinksList: byRole('list', { name: 'overview.external_links' }), - link: byRole('link'), + qualityGateHeader: byRole('heading', { name: 'project.info.quality_gate' }), + qualityProfilesHeader: byRole('heading', { name: 'overview.quality_profiles' }), + externalLinksHeader: byRole('heading', { name: 'overview.external_links' }), tags: byRole('generic', { name: /tags:/ }), size: byRole('link', { name: /project.info.see_more_info_on_x_locs/ }), newKeyInput: byRole('textbox'), @@ -99,10 +98,10 @@ it('should show fields for project', async () => { { currentUser: mockLoggedInUser(), featureList: [Feature.AiCodeAssurance] }, ); expect(await ui.projectPageTitle.find()).toBeInTheDocument(); - expect(ui.qualityGateList.get()).toBeInTheDocument(); - expect(ui.link.getAll(ui.qualityGateList.get())).toHaveLength(1); - expect(ui.link.getAll(ui.qualityProfilesList.get())).toHaveLength(1); - expect(ui.link.getAll(ui.externalLinksList.get())).toHaveLength(1); + expect(ui.qualityGateHeader.get()).toBeInTheDocument(); + expect(byRole('link', { name: /project.info.quality_gate.link_label/ }).getAll()).toHaveLength(1); + expect(byRole('link', { name: /overview.link_to_x_profile_y/ }).getAll()).toHaveLength(1); + expect(byRole('link', { name: 'test' }).getAll()).toHaveLength(1); expect(screen.getByText('project.info.ai_code_assurance.title')).toBeInTheDocument(); expect(screen.getByText('Test description')).toBeInTheDocument(); expect(screen.getByText('my-project')).toBeInTheDocument(); @@ -128,9 +127,9 @@ it('should show application fields', async () => { { currentUser: mockLoggedInUser() }, ); expect(await ui.applicationPageTitle.find()).toBeInTheDocument(); - expect(ui.qualityGateList.query()).not.toBeInTheDocument(); - expect(ui.qualityProfilesList.query()).not.toBeInTheDocument(); - expect(ui.externalLinksList.query()).not.toBeInTheDocument(); + expect(ui.qualityGateHeader.query()).not.toBeInTheDocument(); + expect(ui.qualityProfilesHeader.query()).not.toBeInTheDocument(); + expect(ui.externalLinksHeader.query()).not.toBeInTheDocument(); expect(screen.getByText('Test description')).toBeInTheDocument(); expect(screen.getByText('my-project')).toBeInTheDocument(); expect(screen.getByText('visibility.private')).toBeInTheDocument(); @@ -188,8 +187,8 @@ it('should not show field that is not configured', async () => { qualityProfiles: [], }); expect(await ui.projectPageTitle.find()).toBeInTheDocument(); - expect(ui.qualityGateList.query()).not.toBeInTheDocument(); - expect(ui.qualityProfilesList.query()).not.toBeInTheDocument(); + expect(ui.qualityGateHeader.query()).not.toBeInTheDocument(); + expect(ui.qualityProfilesHeader.query()).not.toBeInTheDocument(); expect(screen.getByText('visibility.public')).toBeInTheDocument(); expect(ui.tags.get()).toHaveTextContent('no_tags'); expect(screen.getByText('project.info.empty_description')).toBeInTheDocument(); @@ -202,8 +201,8 @@ it('should hide visibility if public', async () => { qualityProfiles: [], }); expect(await ui.projectPageTitle.find()).toBeInTheDocument(); - expect(ui.qualityGateList.query()).not.toBeInTheDocument(); - expect(ui.qualityProfilesList.query()).not.toBeInTheDocument(); + expect(ui.qualityGateHeader.query()).not.toBeInTheDocument(); + expect(ui.qualityProfilesHeader.query()).not.toBeInTheDocument(); expect(screen.getByText('visibility.public')).toBeInTheDocument(); expect(ui.tags.get()).toHaveTextContent('no_tags'); expect(screen.getByText('project.info.empty_description')).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx index 27dd155c0fb..c16707829f7 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx @@ -18,10 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Heading } from '@sonarsource/echoes-react'; import classNames from 'classnames'; import { PropsWithChildren, useEffect, useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { BasicSeparator, SubHeading, SubTitle } from '~design-system'; +import { BasicSeparator } from '~design-system'; import { ComponentQualifier, Visibility } from '~sonar-aligned/types/component'; import { getProjectLinks } from '../../../api/projectLinks'; import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures'; @@ -68,9 +69,9 @@ export default function AboutProject(props: AboutProjectProps) { return ( <> -
- {translate(isApp ? 'application' : 'project', 'about.title')} -
+ + {translate(isApp ? 'application' : 'project', 'about.title')} + {!isApp && (component.qualityGate || @@ -88,19 +89,19 @@ export default function AboutProject(props: AboutProjectProps) { {isAiAssured === true && ( - {translate('project.info.ai_code_assurance.title')} - - - + + {translate('project.info.ai_code_assurance.title')} + + )} {component.isAiCodeFixEnabled === true && ( - {translate('project.info.ai_code_fix.title')} - - - + + {translate('project.info.ai_code_fix.title')} + + )} @@ -145,7 +146,7 @@ function ProjectInformationSection(props: PropsWithChildren -
{children}
+
{children}
{!last && } ); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx index 1f8ee2be88d..1fceb061a5c 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { SubHeading, TextMuted } from '~design-system'; +import { Heading, Text } from '@sonarsource/echoes-react'; import { translate } from '../../../../helpers/l10n'; interface Props { @@ -29,11 +29,10 @@ interface Props { export default function MetaDescription({ description, isApp }: Props) { return ( <> - {translate('project.info.description')} - + {translate('project.info.description')} + + {description ?? translate(isApp ? 'application' : 'project', 'info.empty_description')} + ); } diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx index 57d72f61f32..59605f5d494 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx @@ -18,8 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { ClipboardIconButton, CodeSnippet, HelperHintIcon, SubHeading } from '~design-system'; -import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip'; +import { + Button, + ButtonVariety, + Heading, + IconQuestionMark, + Popover, +} from '@sonarsource/echoes-react'; +import { useIntl } from 'react-intl'; +import { ClipboardIconButton, CodeSnippet } from '~design-system'; import { translate } from '../../../../helpers/l10n'; interface MetaKeyProps { @@ -28,26 +35,35 @@ interface MetaKeyProps { } export default function MetaKey({ componentKey, qualifier }: MetaKeyProps) { + const intl = useIntl(); return ( <>
- {translate('overview.project_key', qualifier)} - - {translate('overview.project_key.tooltip', qualifier)} -

- } + {translate('overview.project_key', qualifier)} + - -
+ +
-
-
- - -
+
+ +
); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx index 41d8dbd8e6d..46a49d35429 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { SubHeading } from '~design-system'; +import { Heading } from '@sonarsource/echoes-react'; import MetaLink from '../../../../components/common/MetaLink'; import { translate } from '../../../../helpers/l10n'; import { orderLinks } from '../../../../helpers/projectLinks'; @@ -33,8 +33,8 @@ export default function MetaLinks({ links }: Readonly) { return ( <> - {translate('overview.external_links')} -
    + {translate('overview.external_links')} +
      {orderedLinks.map((link) => ( ))} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx index 58c5d34997e..f06675f8ddf 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { FormattedMessage } from 'react-intl'; -import { Link, Note, StyledMutedText, SubHeading } from '~design-system'; +import { Heading, LinkStandalone, Text } from '@sonarsource/echoes-react'; +import { FormattedMessage, useIntl } from 'react-intl'; import { translate } from '../../../../helpers/l10n'; import { getQualityGateUrl } from '../../../../helpers/urls'; @@ -29,21 +29,33 @@ interface Props { } export default function MetaQualityGate({ qualityGate, isAiAssured }: Props) { + const intl = useIntl(); return ( -
      - {translate('project.info.quality_gate')} - -
        +
        + {translate('project.info.quality_gate')} +
        • - {qualityGate.isDefault && ({translate('default')})} - {qualityGate.name} + {qualityGate.isDefault && ( + + ({translate('default')}) + + )} + + {qualityGate.name} +
        {isAiAssured === true && ( - + - + )} -
      + ); } diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx index b7fb2514ee1..c06a665a363 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx @@ -18,12 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Heading, LinkStandalone, Tooltip } from '@sonarsource/echoes-react'; import React, { useContext, useEffect } from 'react'; -import { Badge, Link, SubHeading } from '~design-system'; +import { Badge } from '~design-system'; import { ComponentQualityProfile } from '~sonar-aligned/types/component'; import { searchRules } from '../../../../api/rules'; import { LanguagesContext } from '../../../../app/components/languages/LanguagesContext'; -import Tooltip from '../../../../components/controls/Tooltip'; import { translate, translateWithParameters } from '../../../../helpers/l10n'; import { getQualityProfileUrl } from '../../../../helpers/urls'; import { Languages } from '../../../../types/languages'; @@ -62,10 +62,9 @@ export function MetaQualityProfiles({ profiles }: Readonly) { }, [profiles]); return ( -
      - {translate('overview.quality_profiles')} - -
        +
        + {translate('overview.quality_profiles')} +
          {profiles.map((profile) => ( ) { /> ))}
        -
      + ); } @@ -93,43 +92,42 @@ function ProfileItem({ const count = deprecatedByKey[profile.key] || 0; return ( -
    • -
      - {languageName} -
      - {profile.deleted ? ( - + {languageName} +
      + {profile.deleted ? ( + +
      {profile.name}
      +
      + ) : ( + <> + -
      {profile.name}
      - - ) : ( - <> - - - {profile.name} + {profile.name} +
      + {count > 0 && ( + + + + {translate('deprecated')} + - - {count > 0 && ( - - - {translate('deprecated')} - - - )} - - )} -
      +
      + )} + + )}
    • ); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx index 63913b08057..be601b713bc 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx @@ -18,7 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { DrilldownLink, Note, SizeIndicator, SubHeading } from '~design-system'; +import { Heading, Link, LinkHighlight, Text, TextSize } from '@sonarsource/echoes-react'; +import { SizeIndicator } from '~design-system'; import { formatMeasure } from '~sonar-aligned/helpers/measures'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; @@ -46,23 +47,25 @@ export default function MetaSize({ component, measures }: MetaSizeProps) { return ( <> -
      - {localizeMetric(MetricKey.ncloc)} +
      + {localizeMetric(MetricKey.ncloc)} ({translate('project.info.main_branch')})
      {ncloc && ncloc.value ? ( <> - - + {formatMeasure(ncloc.value, MetricType.ShortInteger)} - - + + @@ -75,13 +78,17 @@ export default function MetaSize({ component, measures }: MetaSizeProps) { {isApp && ( {projects ? ( - - {formatMeasure(projects.value, MetricType.ShortInteger)} - + + + {formatMeasure(projects.value, MetricType.ShortInteger)} + + ) : ( 0 )} - {translate('metric.projects.name')} + + {translate('metric.projects.name')} + )}
      diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx index 15267e94c9f..96a753c2dee 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx @@ -18,10 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Spinner } from '@sonarsource/echoes-react'; +import { Heading, Spinner } from '@sonarsource/echoes-react'; import { difference, without } from 'lodash'; import { useEffect, useState } from 'react'; -import { MultiSelector, SubHeading, Tags } from '~design-system'; +import { MultiSelector, Tags } from '~design-system'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import { searchProjectTags, setApplicationTags, setProjectTags } from '../../../../api/components'; import Tooltip from '../../../../components/controls/Tooltip'; @@ -74,7 +74,9 @@ export default function MetaTags(props: Props) { return ( <> - {translate('tags')} + + {translate('tags')} + - {translate('visibility')} + + {translate('visibility')} + ); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaQualityProfiles-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaQualityProfiles-test.tsx index e9a4852c8e4..dbfa94dbe8a 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaQualityProfiles-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaQualityProfiles-test.tsx @@ -54,8 +54,12 @@ it('should render correctly', async () => { renderMetaQualityprofiles(); - expect(await screen.findByText('overview.deleted_profile.javascript')).toBeInTheDocument(); - expect(await screen.findByText('overview.deprecated_profile.10')).toBeInTheDocument(); + await expect(await screen.findByText('javascript')).toHaveATooltipWithContent( + 'overview.deleted_profile.javascript', + ); + await expect(await screen.findByText('deprecated')).toHaveATooltipWithContent( + 'overview.deprecated_profile.10', + ); }); function renderMetaQualityprofiles( 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 28cdc20e5d8..551aa7caf8b 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 @@ -21,6 +21,7 @@ import { Spinner } from '@sonarsource/echoes-react'; import { isEmpty } from 'lodash'; import { useState } from 'react'; +import { useIntl } from 'react-intl'; import { BasicSeparator, ButtonSecondary, @@ -58,6 +59,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { branchLike, component: { key: project, qualifier, configuration }, } = props; + const intl = useIntl(); const [selectedType, setSelectedType] = useState(BadgeType.measure); const [selectedMetric, setSelectedMetric] = useState(MetricKey.alert_status); const [selectedFormat, setSelectedFormat] = useState('md'); @@ -94,6 +96,8 @@ export default function ProjectBadges(props: ProjectBadgesProps) { }; const canRenew = configuration?.showSettings; + const selectedMetricOption = metricOptions.find((m) => m.value === selectedMetric); + return (
      {translate('overview.badges.get_badge')} @@ -107,7 +111,10 @@ export default function ProjectBadges(props: ProjectBadgesProps) { selected={BadgeType.measure === selectedType} image={ {translate('overview.badges', } @@ -164,7 +171,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { setSelectedMetric(option.value); } }} - value={metricOptions.find((m) => m.value === selectedMetric)} + value={selectedMetricOption} /> )} @@ -189,6 +196,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { {!isLoading && ( { renderProjectBadges(); await ui.appLoaded(); - expect(screen.getByAltText(`overview.badges.${BadgeType.measure}.alt`)).toHaveAttribute( + expect( + screen.getByAltText(`overview.badges.${BadgeType.measure}.alt.metric.alert_status.name`), + ).toHaveAttribute( 'src', expect.stringContaining( `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=foo`, @@ -75,7 +77,9 @@ it('should renew token', async () => { ), ); - expect(screen.getByAltText(`overview.badges.${BadgeType.measure}.alt`)).toHaveAttribute( + expect( + screen.getByAltText(`overview.badges.${BadgeType.measure}.alt.metric.alert_status.name`), + ).toHaveAttribute( 'src', expect.stringContaining( `host/api/project_badges/measure?branch=branch-6.7&project=my-project&metric=${MetricKey.alert_status}&token=bar`, diff --git a/server/sonar-web/src/main/js/design-system/components/CodeSnippet.tsx b/server/sonar-web/src/main/js/design-system/components/CodeSnippet.tsx index 931031b855a..7adce4394c4 100644 --- a/server/sonar-web/src/main/js/design-system/components/CodeSnippet.tsx +++ b/server/sonar-web/src/main/js/design-system/components/CodeSnippet.tsx @@ -29,6 +29,7 @@ import { ClipboardButton } from './clipboard'; interface Props { className?: string; + copyAriaLabel?: string; isOneLine?: boolean; join?: string; language?: string; @@ -43,16 +44,26 @@ interface Props { const s = ' \\' + '\n '; export function CodeSnippet(props: Readonly) { - const { className, isOneLine, join = s, language, noCopy, render, snippet, wrap = false } = props; + const { + copyAriaLabel, + className, + isOneLine, + join = s, + language, + noCopy, + render, + snippet, + wrap = false, + } = props; const snippetArray = Array.isArray(snippet) ? snippet.filter(isDefined) : [snippet]; const finalSnippet = isOneLine ? snippetArray.join(' ') : snippetArray.join(join); const isSimpleOneLine = isOneLine && noCopy; const copyButton = isOneLine ? ( - + ) : ( - + ); const renderSnippet = @@ -77,7 +88,10 @@ export function CodeSnippet(props: Readonly) { > {!noCopy && copyButton} + {image} {description} @@ -73,11 +77,14 @@ export const StyledSelectionCard = styled(BareButton)` transition: border 0.3s ease; &:hover, - &:focus, &:active { border: ${themeBorder('default', 'primary')}; } + &:focus { + outline: ${themeBorder('focus', 'primary')}; + } + &.selected { border: ${themeBorder('default', 'primary')}; } diff --git a/server/sonar-web/src/main/js/design-system/components/clipboard.tsx b/server/sonar-web/src/main/js/design-system/components/clipboard.tsx index bb7f6bbd0f2..45a6b778cd3 100644 --- a/server/sonar-web/src/main/js/design-system/components/clipboard.tsx +++ b/server/sonar-web/src/main/js/design-system/components/clipboard.tsx @@ -34,6 +34,7 @@ import React, { ComponentProps, useCallback, useState } from 'react'; const COPY_SUCCESS_NOTIFICATION_LIFESPAN = 1000; interface ButtonProps { + ariaLabel?: string; children?: React.ReactNode; className?: string; copiedLabel?: string; @@ -48,6 +49,7 @@ export function ClipboardButton(props: ButtonProps) { className, children, copyValue, + ariaLabel, copiedLabel = 'Copied', copyLabel = 'Copy', } = props; @@ -58,6 +60,7 @@ export function ClipboardButton(props: ButtonProps) { {/* TODO ^ Remove TooltipProvider after design-system is reintegrated into sonar-web */}