diff options
author | Stanislav <31501873+stanislavhh@users.noreply.github.com> | 2024-09-12 10:38:19 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-09-12 20:02:54 +0000 |
commit | 5aeed16a227d13f8c7632a578c8bb84613fbb836 (patch) | |
tree | d5136dbacc81670b0a4bca43b444942746afd8a1 /server | |
parent | b135083b243a5543f43b8d81dc2a5c53931fc460 (diff) | |
download | sonarqube-5aeed16a227d13f8c7632a578c8bb84613fbb836.tar.gz sonarqube-5aeed16a227d13f8c7632a578c8bb84613fbb836.zip |
SONAR-22301 Fix accessibility issues on project overview page (#11729)
Diffstat (limited to 'server')
7 files changed, 81 insertions, 94 deletions
diff --git a/server/sonar-web/design-system/src/components/icons/TrendIcon.tsx b/server/sonar-web/design-system/src/components/icons/TrendIcon.tsx index cd93d9c6ff3..331c0b513d9 100644 --- a/server/sonar-web/design-system/src/components/icons/TrendIcon.tsx +++ b/server/sonar-web/design-system/src/components/icons/TrendIcon.tsx @@ -17,9 +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 { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { + IconArrowDownRight, + IconArrowUpRight, + IconEqual, + IconProps, +} from '@sonarsource/echoes-react'; import { themeColor } from '../../helpers/theme'; -import { CustomIcon, IconProps } from './Icon'; +import { ThemeColors } from '../../types'; export const enum TrendDirection { Down = 'down', @@ -40,46 +46,40 @@ interface Props extends IconProps { } export function TrendIcon(props: Readonly<Props>) { - const theme = useTheme(); const { direction, type, ...iconProps } = props; - const fill = themeColor( - ( - { - [TrendType.Positive]: 'iconTrendPositive', - [TrendType.Negative]: 'iconTrendNegative', - [TrendType.Neutral]: 'iconTrendNeutral', - [TrendType.Disabled]: 'iconTrendDisabled', - } as const - )[type], - )({ theme }); + if (direction === TrendDirection.Up) { + return ( + <TrendIconWrapper trendType={type}> + <IconArrowUpRight aria-label="trend-up" {...iconProps} /> + </TrendIconWrapper> + ); + } + + if (direction === TrendDirection.Down) { + return ( + <TrendIconWrapper trendType={type}> + <IconArrowDownRight aria-label="trend-down" {...iconProps} /> + </TrendIconWrapper> + ); + } return ( - <CustomIcon {...iconProps}> - {direction === TrendDirection.Up && ( - <path - aria-label="trend-up" - clipRule="evenodd" - d="M4.75802 4.3611a.74997.74997 0 0 1 .74953-.74953H11.518a.74985.74985 0 0 1 .5298.21967.74967.74967 0 0 1 .2197.52986v6.0104a.75043.75043 0 0 1-.2286.5132.75053.75053 0 0 1-.5209.2104.75017.75017 0 0 1-.5209-.2104.75054.75054 0 0 1-.2287-.5132V6.1713l-5.26085 5.2609a.75014.75014 0 0 1-1.06066 0 .75004.75004 0 0 1 0-1.0607l5.26088-5.26086H5.50755a.75001.75001 0 0 1-.74953-.74954Z" - fill={fill} - fillRule="evenodd" - /> - )} - {direction === TrendDirection.Down && ( - <path - aria-label="trend-down" - clipRule="evenodd" - d="M11.5052 4.14237a.75026.75026 0 0 1 .5299.21967.74997.74997 0 0 1 .2196.52986v6.0104a.7501.7501 0 0 1-.2196.5299.75027.75027 0 0 1-.5299.2196H5.49479a.74976.74976 0 0 1-.51314-.2286.75004.75004 0 0 1 0-1.0418.74976.74976 0 0 1 .51314-.2286h4.20022L4.43413 4.8919a.75001.75001 0 0 1 1.06066-1.06066l5.26091 5.26087V4.8919a.74997.74997 0 0 1 .2196-.52986.75008.75008 0 0 1 .5299-.21967Z" - fill={fill} - fillRule="evenodd" - /> - )} - {direction === TrendDirection.Equal && ( - <g aria-label="trend-equal"> - <rect fill={fill} height="1.5" rx=".75" width="8" x="4" y="5" /> - <rect fill={fill} height="1.5" rx=".75" width="8" x="4" y="9" /> - </g> - )} - </CustomIcon> + <TrendIconWrapper trendType={type}> + <IconEqual aria-label="trend-equal" {...iconProps} /> + </TrendIconWrapper> ); } + +const ICON_COLORS: Record<TrendType, ThemeColors> = { + [TrendType.Positive]: 'iconTrendPositive', + [TrendType.Negative]: 'iconTrendNegative', + [TrendType.Neutral]: 'iconTrendNeutral', + [TrendType.Disabled]: 'iconTrendDisabled', +}; + +const TrendIconWrapper = styled.span<{ + trendType: TrendType; +}>` + color: ${({ trendType }) => themeColor(ICON_COLORS[trendType])}; +`; diff --git a/server/sonar-web/design-system/src/theme/light.ts b/server/sonar-web/design-system/src/theme/light.ts index ddc35bf7215..ef98fce9ac9 100644 --- a/server/sonar-web/design-system/src/theme/light.ts +++ b/server/sonar-web/design-system/src/theme/light.ts @@ -420,7 +420,7 @@ export const lightTheme = { iconNegativeUpdate: COLORS.red[300], iconTrendPositive: COLORS.green[400], iconTrendNegative: COLORS.red[400], - iconTrendNeutral: COLORS.blue[400], + iconTrendNeutral: COLORS.blue[600], iconTrendDisabled: COLORS.blueGrey[400], iconError: danger.default, iconWarning: COLORS.yellow[600], diff --git a/server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx b/server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx index afcd4b455a9..9c05d456efe 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx @@ -67,15 +67,7 @@ function Variation(props: Readonly<VariationProps>) { trendIconDirection = variation > 0 ? TrendDirection.Up : TrendDirection.Down; trendIconType = variation > 0 === isGoodIfGrowing ? TrendType.Positive : TrendType.Negative; } - const variationIcon = ( - <TrendIcon - className="sw-text-lg" - direction={trendIconDirection} - height={20} - type={trendIconType} - width={20} - /> - ); + const variationIcon = <TrendIcon direction={trendIconDirection} type={trendIconType} />; const variationToDisplay = formattedValue.startsWith('-') ? formattedValue : `+${formattedValue}`; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx index 1f4bbdcaede..e5c87c3ab8e 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.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 styled from '@emotion/styled'; import { LinkBox, TextMuted } from 'design-system'; import * as React from 'react'; import { Path } from 'react-router-dom'; @@ -107,7 +108,11 @@ export class QualityGateCondition extends React.PureComponent<Props> { }; if (METRICS_TO_URL_MAPPING[metricKey]) { - return <LinkBox to={METRICS_TO_URL_MAPPING[metricKey]()}>{children}</LinkBox>; + return ( + <LinkBox className="link-box-wrapper" to={METRICS_TO_URL_MAPPING[metricKey]()}> + {children} + </LinkBox> + ); } const url = isIssueMeasure(condition.measure.metric.key) @@ -122,7 +127,11 @@ export class QualityGateCondition extends React.PureComponent<Props> { listView: true, }); - return <LinkBox to={url}>{children}</LinkBox>; + return ( + <LinkBox className="link-box-wrapper" to={url}> + {children} + </LinkBox> + ); } getPrimaryText = () => { @@ -173,7 +182,7 @@ export class QualityGateCondition extends React.PureComponent<Props> { {this.getPrimaryText()} </span> </div> - <TextMuted text={`${operator} ${formatMeasure(threshold, metric.type)}`} /> + <StyledMutedText text={`${operator} ${formatMeasure(threshold, metric.type)}`} /> </div> </div>, ); @@ -181,3 +190,9 @@ export class QualityGateCondition extends React.PureComponent<Props> { } export default withMetricsContext(QualityGateCondition); + +const StyledMutedText = styled(TextMuted)` + .link-box-wrapper:hover & { + color: unset; + } +`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx index 4cf9ca58475..1e29192a079 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx @@ -111,7 +111,7 @@ describe('rendering', () => { renderProjectActivityAppContainer(); await ui.appLoaded(); - expect(ui.graphTypeIssues.get()).toBeInTheDocument(); + expect(ui.graphTypeSelect.get()).toHaveValue('project_activity.graphs.issues'); }); it('should render new code legend for applications', async () => { @@ -402,7 +402,7 @@ describe('data loading', () => { renderProjectActivityAppContainer(); await ui.appLoaded(); - expect(ui.graphTypeCustom.get()).toBeInTheDocument(); + expect(ui.graphTypeSelect.get()).toHaveValue('project_activity.graphs.custom'); }); it('should correctly fetch the top level component when dealing with sub portfolios', async () => { @@ -756,8 +756,6 @@ function getPageObject() { const ui = { // Graph types. graphTypeSelect: byLabelText('project_activity.graphs.choose_type'), - graphTypeIssues: byText('project_activity.graphs.issues'), - graphTypeCustom: byText('project_activity.graphs.custom'), // Graphs. graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }), diff --git a/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx index b79add8c0f4..1ef69f5ddf1 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx +++ b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx @@ -19,7 +19,7 @@ */ import { Button, IconChevronDown } from '@sonarsource/echoes-react'; -import { Dropdown, TextMuted } from 'design-system'; +import { Dropdown } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; import { MetricKey, MetricType } from '~sonar-aligned/types/metrics'; @@ -165,10 +165,9 @@ export default class AddGraphMetric extends React.PureComponent<Props, State> { } > <Button suffix={<IconChevronDown />}> - <TextMuted - className="sw-body-sm sw-flex" - text={translate('project_activity.graphs.custom.add')} - /> + <span className="sw-body-sm sw-flex"> + {translate('project_activity.graphs.custom.add')} + </span> </Button> </Dropdown> ); diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx index 8390f89174f..b50b4413050 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx @@ -18,14 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { - Button, - ButtonGroup, - DropdownMenu, - DropdownMenuAlign, - IconChevronDown, -} from '@sonarsource/echoes-react'; -import { TextMuted } from 'design-system'; +import { ButtonGroup, InputSize, Select } from '@sonarsource/echoes-react'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { GraphType } from '../../types/project-activity'; @@ -66,34 +59,24 @@ export default function GraphsHeader(props: Props) { const noCustomGraph = props.onAddCustomMetric === undefined || props.onRemoveCustomMetric === undefined; - const options = React.useMemo(() => { - const types = getGraphTypes(noCustomGraph); - - return types.map((type) => { - const label = translate('project_activity.graphs', type); - - return ( - <DropdownMenu.ItemButton key={label} onClick={() => handleGraphChange(type)}> - {label} - </DropdownMenu.ItemButton> - ); - }); - }, [noCustomGraph, handleGraphChange]); - return ( <div className={className}> <ButtonGroup> - <DropdownMenu.Root align={DropdownMenuAlign.Start} id="activity-graph-type" items={options}> - <Button - aria-label={translate('project_activity.graphs.choose_type')} - suffix={<IconChevronDown />} - > - <TextMuted - className="sw-body-sm sw-flex" - text={translate('project_activity.graphs', graph)} - /> - </Button> - </DropdownMenu.Root> + <label htmlFor="graph-type" className="sw-body-sm-highlight"> + {translate('project_activity.graphs.choose_type')} + </label> + <Select + id="graph-type" + hasDropdownAutoWidth + onChange={handleGraphChange} + isNotClearable + value={graph} + size={InputSize.Small} + data={getGraphTypes(noCustomGraph).map((type) => ({ + value: type, + label: translate('project_activity.graphs', type), + }))} + /> {isCustomGraph(graph) && props.onAddCustomMetric !== undefined && |