aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2024-05-21 17:05:03 +0200
committersonartech <sonartech@sonarsource.com>2024-05-27 20:02:40 +0000
commit1953626fabdb68e2f271899a16ad30faf4c2a7c8 (patch)
tree3d1acc8d4f7ce29d62f92e28d53c10f2d04c552e /server/sonar-web
parent489cffe6250345b7718bfa0d216c7741f40e6b41 (diff)
downloadsonarqube-1953626fabdb68e2f271899a16ad30faf4c2a7c8.tar.gz
sonarqube-1953626fabdb68e2f271899a16ad30faf4c2a7c8.zip
SONAR-22218 Deprecate old tooltips and migrate some of them
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/config/jest/SetupReactTestingLibrary.ts10
-rw-r--r--server/sonar-web/design-system/src/components/DropdownMenu.tsx38
-rw-r--r--server/sonar-web/design-system/src/components/InteractiveIcon.tsx96
-rw-r--r--server/sonar-web/design-system/src/components/Tooltip.tsx26
-rw-r--r--server/sonar-web/design-system/src/components/index.ts1
-rw-r--r--server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx49
-rw-r--r--server/sonar-web/design-system/src/sonar-aligned/components/buttons/Button.tsx105
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationDetails.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx24
-rw-r--r--server/sonar-web/src/main/js/components/controls/Tooltip.css9
-rw-r--r--server/sonar-web/src/main/js/components/controls/Tooltip.tsx15
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx18
-rw-r--r--server/sonar-web/src/main/js/components/tags/TagsList.css36
-rw-r--r--server/sonar-web/src/main/js/components/tags/TagsList.tsx4
-rw-r--r--server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx17
-rw-r--r--server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx10
24 files changed, 306 insertions, 327 deletions
diff --git a/server/sonar-web/config/jest/SetupReactTestingLibrary.ts b/server/sonar-web/config/jest/SetupReactTestingLibrary.ts
index 35347ca7ead..346d4a2cb87 100644
--- a/server/sonar-web/config/jest/SetupReactTestingLibrary.ts
+++ b/server/sonar-web/config/jest/SetupReactTestingLibrary.ts
@@ -18,7 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import '@testing-library/jest-dom';
-import { configure, fireEvent, screen, waitFor } from '@testing-library/react';
+import { configure, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
configure({
asyncUtilTimeout: 3000,
@@ -26,6 +27,8 @@ configure({
expect.extend({
async toHaveATooltipWithContent(received: any, content: string) {
+ const user = userEvent.setup();
+
if (!(received instanceof Element)) {
return {
pass: false,
@@ -33,7 +36,8 @@ expect.extend({
};
}
- fireEvent.pointerEnter(received);
+ await user.hover(received);
+
const tooltip = await screen.findByRole('tooltip');
const result = tooltip.textContent?.includes(content)
@@ -47,7 +51,7 @@ expect.extend({
`Tooltip content "${tooltip.textContent}" does not contain expected "${content}"`,
};
- fireEvent.pointerLeave(received);
+ await user.keyboard('{Escape}');
await waitFor(() => {
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
diff --git a/server/sonar-web/design-system/src/components/DropdownMenu.tsx b/server/sonar-web/design-system/src/components/DropdownMenu.tsx
index d5ba01e2485..162b11f9c0b 100644
--- a/server/sonar-web/design-system/src/components/DropdownMenu.tsx
+++ b/server/sonar-web/design-system/src/components/DropdownMenu.tsx
@@ -20,7 +20,7 @@
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import classNames from 'classnames';
-import React from 'react';
+import React, { ForwardedRef, forwardRef } from 'react';
import tw from 'twin.macro';
import { INPUT_SIZES } from '../helpers/constants';
import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
@@ -138,22 +138,26 @@ interface ItemButtonProps extends ListItemProps {
onClick: React.MouseEventHandler<HTMLButtonElement>;
}
-export function ItemButton(props: ItemButtonProps) {
- const { children, className, disabled, icon, innerRef, onClick, selected, ...liProps } = props;
- return (
- <li ref={innerRef} role="none" {...liProps}>
- <ItemButtonStyled
- className={classNames(className, { disabled, selected })}
- disabled={disabled}
- onClick={onClick}
- role="menuitem"
- >
- {icon}
- {children}
- </ItemButtonStyled>
- </li>
- );
-}
+export const ItemButton = forwardRef(
+ (props: ItemButtonProps, ref: ForwardedRef<HTMLButtonElement>) => {
+ const { children, className, disabled, icon, innerRef, onClick, selected, ...liProps } = props;
+ return (
+ <li ref={innerRef} role="none" {...liProps}>
+ <ItemButtonStyled
+ className={classNames(className, { disabled, selected })}
+ disabled={disabled}
+ onClick={onClick}
+ ref={ref}
+ role="menuitem"
+ >
+ {icon}
+ {children}
+ </ItemButtonStyled>
+ </li>
+ );
+ },
+);
+ItemButton.displayName = 'ItemButton';
export const ItemDangerButton = styled(ItemButton)`
--color: ${themeContrast('dropdownMenuDanger')};
diff --git a/server/sonar-web/design-system/src/components/InteractiveIcon.tsx b/server/sonar-web/design-system/src/components/InteractiveIcon.tsx
index accd153c954..9458645fa21 100644
--- a/server/sonar-web/design-system/src/components/InteractiveIcon.tsx
+++ b/server/sonar-web/design-system/src/components/InteractiveIcon.tsx
@@ -20,13 +20,12 @@
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import classNames from 'classnames';
-import React from 'react';
+import React, { ForwardedRef, MouseEvent, forwardRef, useCallback } from 'react';
import tw from 'twin.macro';
import { OPACITY_20_PERCENT } from '../helpers/constants';
import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
import { isDefined } from '../helpers/types';
import { ThemedProps } from '../types/theme';
-import { BaseLink, LinkProps } from './Link';
import { IconProps } from './icons/Icon';
export type InteractiveIconSize = 'small' | 'medium';
@@ -41,63 +40,54 @@ export interface InteractiveIconProps {
iconProps?: IconProps;
id?: string;
innerRef?: React.Ref<HTMLButtonElement>;
- onClick?: VoidFunction;
+ onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
size?: InteractiveIconSize;
stopPropagation?: boolean;
- to?: LinkProps['to'];
}
-export class InteractiveIconBase extends React.PureComponent<InteractiveIconProps> {
- handleClick = (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
- const { disabled, onClick, stopPropagation = true } = this.props;
-
- if (stopPropagation) {
- event.stopPropagation();
- }
-
- if (onClick && !disabled) {
- onClick();
- }
- };
-
- render() {
+export const InteractiveIconBase = forwardRef(
+ (props: InteractiveIconProps, ref: ForwardedRef<HTMLButtonElement>) => {
const {
Icon,
children,
disabled,
- innerRef,
onClick,
size = 'medium',
- to,
iconProps = {},
+ stopPropagation = true,
...htmlProps
- } = this.props;
+ } = props;
+
+ const handleClick = useCallback(
+ (event: React.MouseEvent<HTMLButtonElement>) => {
+ if (stopPropagation) {
+ event.stopPropagation();
+ }
+
+ if (onClick && !disabled) {
+ onClick(event);
+ }
+ },
+ [disabled, onClick, stopPropagation],
+ );
- const props = {
+ const propsForInteractiveWrapper = {
...htmlProps,
'aria-disabled': disabled,
disabled,
size,
- type: 'button' as const,
};
- if (to) {
- return (
- <IconLink {...props} onClick={onClick} showExternalIcon={false} stopPropagation to={to}>
- <Icon className={classNames({ 'sw-mr-1': isDefined(children) })} {...iconProps} />
- {children}
- </IconLink>
- );
- }
-
return (
- <IconButton {...props} onClick={this.handleClick} ref={innerRef}>
+ <IconButton {...propsForInteractiveWrapper} onClick={handleClick} ref={ref} type="button">
<Icon className={classNames({ 'sw-mr-1': isDefined(children) })} {...iconProps} />
{children}
</IconButton>
);
- }
-}
+ },
+);
+
+InteractiveIconBase.displayName = 'InteractiveIconBase';
const buttonIconStyle = (props: ThemedProps & { size: InteractiveIconSize }) => css`
box-sizing: border-box;
@@ -141,17 +131,11 @@ const buttonIconStyle = (props: ThemedProps & { size: InteractiveIconSize }) =>
}
`;
-const IconLink = styled(BaseLink)`
- ${buttonIconStyle}
-`;
-
const IconButton = styled.button`
${buttonIconStyle}
`;
-export const InteractiveIcon: React.FC<React.PropsWithChildren<InteractiveIconProps>> = styled(
- InteractiveIconBase,
-)`
+export const InteractiveIcon = styled(InteractiveIconBase)`
--background: ${themeColor('interactiveIcon')};
--backgroundHover: ${themeColor('interactiveIconHover')};
--color: ${({ currentColor, theme }) =>
@@ -160,14 +144,11 @@ export const InteractiveIcon: React.FC<React.PropsWithChildren<InteractiveIconPr
--focus: ${themeColor('interactiveIconFocus', OPACITY_20_PERCENT)};
`;
-export const DiscreetInteractiveIcon: React.FC<React.PropsWithChildren<InteractiveIconProps>> =
- styled(InteractiveIcon)`
- --color: ${themeColor('discreetInteractiveIcon')};
- `;
+export const DiscreetInteractiveIcon = styled(InteractiveIcon)`
+ --color: ${themeColor('discreetInteractiveIcon')};
+`;
-export const DestructiveIcon: React.FC<React.PropsWithChildren<InteractiveIconProps>> = styled(
- InteractiveIconBase,
-)`
+export const DestructiveIcon = styled(InteractiveIconBase)`
--background: ${themeColor('destructiveIcon')};
--backgroundHover: ${themeColor('destructiveIconHover')};
--color: ${themeContrast('destructiveIcon')};
@@ -175,13 +156,12 @@ export const DestructiveIcon: React.FC<React.PropsWithChildren<InteractiveIconPr
--focus: ${themeColor('destructiveIconFocus', OPACITY_20_PERCENT)};
`;
-export const DismissProductNewsIcon: React.FC<React.PropsWithChildren<InteractiveIconProps>> =
- styled(InteractiveIcon)`
- --background: ${themeColor('productNews')};
- --backgroundHover: ${themeColor('productNewsHover')};
- --color: ${themeContrast('productNews')};
- --colorHover: ${themeContrast('productNewsHover')};
- --focus: ${themeColor('interactiveIconFocus', OPACITY_20_PERCENT)};
+export const DismissProductNewsIcon = styled(InteractiveIcon)`
+ --background: ${themeColor('productNews')};
+ --backgroundHover: ${themeColor('productNewsHover')};
+ --color: ${themeContrast('productNews')};
+ --colorHover: ${themeContrast('productNewsHover')};
+ --focus: ${themeColor('interactiveIconFocus', OPACITY_20_PERCENT)};
- height: 28px;
- `;
+ height: 28px;
+`;
diff --git a/server/sonar-web/design-system/src/components/Tooltip.tsx b/server/sonar-web/design-system/src/components/Tooltip.tsx
index b92c841ad68..053c54d9033 100644
--- a/server/sonar-web/design-system/src/components/Tooltip.tsx
+++ b/server/sonar-web/design-system/src/components/Tooltip.tsx
@@ -31,11 +31,11 @@ import {
PopupPlacement,
popupPositioning,
} from '../helpers/positioning';
-import { themeColor, themeContrast } from '../helpers/theme';
+import { themeColor } from '../helpers/theme';
const MILLISECONDS_IN_A_SECOND = 1000;
-export interface TooltipProps {
+interface TooltipProps {
children: React.ReactElement;
mouseEnterDelay?: number;
mouseLeaveDelay?: number;
@@ -67,6 +67,19 @@ function isMeasured(state: State): state is OwnState & Measurements {
return state.height !== undefined;
}
+/** @deprecated Use {@link Echoes.Tooltip | Tooltip} from Echoes instead.
+ *
+ * Echoes Tooltip component should mainly be used on interactive element and contain very simple text based content.
+ * If the content is more complex use a Popover component instead (not available yet).
+ *
+ * Some of the props have changed or been renamed:
+ * - `children` is the trigger for the tooltip, should be an interactive Element. If not an Echoes component, make sure the component forwards the props and the ref to an interactive DOM node, it's needed by the tooltip to position itself.
+ * - `overlay` is now `content`, that's the tooltip content. It's a ReactNode for convenience but should render only text based content, no interactivity is allowed inside the tooltip.
+ * - ~`mouseEnterDelay`~ doesn't exist anymore, was mostly used in situation that should be replaced by a Popover component.
+ * - ~`mouseLeaveDelay`~ doesn't exist anymore, was mostly used in situation that should be replaced by a Popover component.
+ * - `placement` is now `align` and `side`, based on the {@link Echoes.TooltipAlign | TooltipAlign} and {@link Echoes.TooltipSide | TooltipSide} enums.
+ * - `visible` is now `isOpen`
+ */
export function Tooltip(props: TooltipProps) {
// overlay is a ReactNode, so it can be a boolean, `undefined` or `null`
// this allows to easily render a tooltip conditionally
@@ -517,16 +530,17 @@ const TooltipWrapperArrow = styled.div`
`;
export const TooltipWrapperInner = styled.div`
- color: ${themeContrast('tooltipBackground')};
- background-color: ${themeColor('tooltipBackground')};
+ font: var(--echoes-typography-paragraph-small-regular);
+ padding: var(--echoes-dimension-space-50) var(--echoes-dimension-space-150);
+ color: var(--echoes-color-text-on-color);
+ background-color: var(--echoes-color-background-inverse);
+ border-radius: var(--echoes-border-radius-200);
${tw`sw-max-w-[22rem]`}
- ${tw`sw-py-3 sw-px-4`};
${tw`sw-overflow-hidden`};
${tw`sw-text-left`};
${tw`sw-no-underline`};
${tw`sw-break-words`};
- ${tw`sw-rounded-2`};
hr {
background-color: ${themeColor('tooltipSeparator')};
diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts
index a2d565d15f2..f5155fd7f1e 100644
--- a/server/sonar-web/design-system/src/components/index.ts
+++ b/server/sonar-web/design-system/src/components/index.ts
@@ -74,7 +74,6 @@ export * from './Tabs';
export * from './Tags';
export * from './Text';
export * from './TextAccordion';
-export { Tooltip } from './Tooltip';
export { TopBar } from './TopBar';
export * from './TreeMap';
export * from './TreeMapRect';
diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx
index 9bdae72f604..bf1555b1b93 100644
--- a/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx
+++ b/server/sonar-web/design-system/src/sonar-aligned/components/MetricsRatingBadge.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
+import { forwardRef } from 'react';
import tw from 'twin.macro';
import { getProp, themeColor, themeContrast } from '../../helpers/theme';
import { RatingLabel } from '../types/measures';
@@ -38,37 +39,37 @@ const SIZE_MAPPING = {
xl: '4rem',
};
-export function MetricsRatingBadge({
- className,
- size = 'sm',
- label,
- rating,
- ...ariaAttrs
-}: Readonly<Props>) {
- if (!rating) {
+export const MetricsRatingBadge = forwardRef<HTMLDivElement, Props>(
+ ({ className, size = 'sm', label, rating, ...ariaAttrs }: Readonly<Props>, ref) => {
+ if (!rating) {
+ return (
+ <StyledNoRatingBadge
+ aria-label={label}
+ className={className}
+ ref={ref}
+ size={SIZE_MAPPING[size]}
+ {...ariaAttrs}
+ >
+ —
+ </StyledNoRatingBadge>
+ );
+ }
return (
- <StyledNoRatingBadge
+ <MetricsRatingBadgeStyled
aria-label={label}
className={className}
+ rating={rating}
+ ref={ref}
size={SIZE_MAPPING[size]}
{...ariaAttrs}
>
- —
- </StyledNoRatingBadge>
+ {rating}
+ </MetricsRatingBadgeStyled>
);
- }
- return (
- <MetricsRatingBadgeStyled
- aria-label={label}
- className={className}
- rating={rating}
- size={SIZE_MAPPING[size]}
- {...ariaAttrs}
- >
- {rating}
- </MetricsRatingBadgeStyled>
- );
-}
+ },
+);
+
+MetricsRatingBadge.displayName = 'MetricsRatingBadge';
const StyledNoRatingBadge = styled.div<{ size: string }>`
display: inline-flex;
diff --git a/server/sonar-web/design-system/src/sonar-aligned/components/buttons/Button.tsx b/server/sonar-web/design-system/src/sonar-aligned/components/buttons/Button.tsx
index db5693e7f63..28fb7f87113 100644
--- a/server/sonar-web/design-system/src/sonar-aligned/components/buttons/Button.tsx
+++ b/server/sonar-web/design-system/src/sonar-aligned/components/buttons/Button.tsx
@@ -19,7 +19,7 @@
*/
import { css } from '@emotion/react';
import styled from '@emotion/styled';
-import React from 'react';
+import React, { MouseEvent, ReactNode, forwardRef, useCallback } from 'react';
import tw from 'twin.macro';
import { BaseLink, LinkProps } from '../../../components/Link';
import { themeBorder, themeColor, themeContrast } from '../../../helpers/theme';
@@ -31,15 +31,14 @@ type AllowedButtonAttributes = Pick<
>;
export interface ButtonProps extends AllowedButtonAttributes {
- children?: React.ReactNode;
+ children?: ReactNode;
className?: string;
disabled?: boolean;
download?: string;
- icon?: React.ReactNode;
- innerRef?: React.Ref<HTMLButtonElement>;
+ icon?: ReactNode;
isExternal?: LinkProps['isExternal'];
+ onClick?: (event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => unknown;
- onClick?: (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => unknown;
preventDefault?: boolean;
reloadDocument?: LinkProps['reloadDocument'];
showExternalIcon?: boolean;
@@ -48,62 +47,60 @@ export interface ButtonProps extends AllowedButtonAttributes {
to?: LinkProps['to'];
}
-export class Button extends React.PureComponent<ButtonProps> {
- handleClick = (event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
- const { disabled, onClick, stopPropagation = false, type } = this.props;
- const { preventDefault = type !== 'submit' } = this.props;
-
- if (preventDefault || disabled) {
- event.preventDefault();
- }
-
- if (stopPropagation) {
- event.stopPropagation();
- }
-
- if (onClick && !disabled) {
- onClick(event);
- }
+export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
+ const {
+ children,
+ disabled,
+ icon,
+ onClick,
+ preventDefault = props.type !== 'submit',
+ stopPropagation = false,
+ to,
+ type = 'button',
+ ...htmlProps
+ } = props;
+
+ const handleClick = useCallback(
+ (event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
+ if (preventDefault || disabled) {
+ event.preventDefault();
+ }
+
+ if (stopPropagation) {
+ event.stopPropagation();
+ }
+
+ if (onClick && !disabled) {
+ onClick(event);
+ }
+ },
+ [disabled, onClick, preventDefault, stopPropagation],
+ );
+
+ const buttonProps = {
+ ...htmlProps,
+ 'aria-disabled': disabled,
+ disabled,
+ type,
};
- render() {
- const {
- children,
- disabled,
- icon,
- innerRef,
- onClick,
- preventDefault,
- stopPropagation,
- to,
- type = 'button',
- ...htmlProps
- } = this.props;
-
- const props = {
- ...htmlProps,
- 'aria-disabled': disabled,
- disabled,
- type,
- };
-
- if (to) {
- return (
- <BaseButtonLink {...props} onClick={onClick} to={to}>
- {icon}
- {children}
- </BaseButtonLink>
- );
- }
-
+ if (to) {
return (
- <BaseButton {...props} onClick={this.handleClick} ref={innerRef}>
+ <BaseButtonLink {...buttonProps} onClick={onClick} to={to}>
{icon}
{children}
- </BaseButton>
+ </BaseButtonLink>
);
}
-}
+
+ return (
+ <BaseButton {...buttonProps} onClick={handleClick} ref={ref}>
+ {icon}
+ {children}
+ </BaseButton>
+ );
+});
+Button.displayName = 'Button';
export const buttonStyle = (props: ThemedProps) => css`
box-sizing: border-box;
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
index cb034260408..5116cc33a10 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx
@@ -19,6 +19,8 @@
*/
import { ThemeProvider } from '@emotion/react';
+import styled from '@emotion/styled';
+import { TooltipProvider } from '@sonarsource/echoes-react';
import { QueryClientProvider } from '@tanstack/react-query';
import { ToastMessageContainer, lightTheme } from 'design-system';
import * as React from 'react';
@@ -275,7 +277,11 @@ export default function startReactApp(
<GlobalStyles />
<ToastMessageContainer />
<Helmet titleTemplate={translate('page_title.template.default')} />
- <RouterProvider router={router} />
+ <StackContext>
+ <TooltipProvider>
+ <RouterProvider router={router} />
+ </TooltipProvider>
+ </StackContext>
</QueryClientProvider>
</ThemeProvider>
</RawIntlProvider>
@@ -285,3 +291,12 @@ export default function startReactApp(
</HelmetProvider>,
);
}
+
+/*
+ * This ensures tooltips and other "floating" elements appended to the body are placed on top
+ * of the rest of the UI.
+ */
+const StackContext = styled.div`
+ z-index: 0;
+ position: relative;
+`;
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx
index 83393439073..f4320b01d53 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx
@@ -17,7 +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 { LightLabel, Note, SeparatorCircleIcon, Tooltip } from 'design-system';
+import { Tooltip } from '@sonarsource/echoes-react';
+import { LightLabel, Note, SeparatorCircleIcon } from 'design-system';
import React from 'react';
import DateFromNow from '../../../components/intl/DateFromNow';
import IssueSeverity from '../../../components/issue/components/IssueSeverity';
@@ -62,11 +63,11 @@ export default function IssueHeaderMeta({ issue }: Readonly<Props>) {
</div>
<SeparatorCircleIcon />
- {!!issue.codeVariants?.length && (
+ {(issue.codeVariants?.length ?? 0) > 0 && (
<>
<div className="sw-flex sw-gap-1">
<span>{translate('issue.code_variants')}</span>
- <Tooltip overlay={issue.codeVariants?.join(', ')}>
+ <Tooltip content={issue.codeVariants?.join(', ')}>
<span className="sw-font-semibold">
<LightLabel>{issue.codeVariants?.join(', ')}</LightLabel>
</span>
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx
index 97a2b330269..73d7c60c3ee 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx
@@ -18,8 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
+import { Tooltip } from '@sonarsource/echoes-react';
import classNames from 'classnames';
-import { DiscreetLinkBox, Tooltip, themeColor, themeContrast } from 'design-system';
+import { DiscreetLinkBox, themeColor, themeContrast } from 'design-system';
import * as React from 'react';
import { useIntl } from 'react-intl';
import { formatMeasure } from '~sonar-aligned/helpers/measures';
@@ -72,7 +73,7 @@ export function SoftwareImpactMeasureBreakdownCard(
return (
<Tooltip
- overlay={intl.formatMessage({
+ content={intl.formatMessage({
id: `overview.measures.software_impact.severity.${severity}.tooltip`,
})}
>
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
index 0586663c04d..00093074e7c 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
@@ -18,14 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
-import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
+import { LinkHighlight, LinkStandalone, Tooltip } from '@sonarsource/echoes-react';
import { Badge, LightGreyCard, LightGreyCardTitle, TextBold, TextSubdued } from 'design-system';
import * as React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { formatMeasure } from '~sonar-aligned/helpers/measures';
import { getComponentIssuesUrl } from '~sonar-aligned/helpers/urls';
import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
-import Tooltip from '../../../components/controls/Tooltip';
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import {
SOFTWARE_QUALITIES_METRIC_KEYS_MAP,
@@ -109,7 +108,7 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
<div className="sw-flex sw-mt-4">
<div className="sw-flex sw-gap-1 sw-items-center">
{count ? (
- <Tooltip overlay={countTooltipOverlay}>
+ <Tooltip content={countTooltipOverlay}>
<LinkStandalone
data-testid={`overview__software-impact-${softwareQuality}`}
aria-label={intl.formatMessage(
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx
index 1f6c0edaaa4..6f0f1f4bc7f 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureRating.tsx
@@ -17,7 +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 { MetricsRatingBadge, Tooltip } from 'design-system';
+import { Tooltip } from '@sonarsource/echoes-react';
+import { MetricsRatingBadge } from 'design-system';
import * as React from 'react';
import { useIntl } from 'react-intl';
import { formatRating } from '../../../helpers/measures';
@@ -36,28 +37,33 @@ export function SoftwareImpactMeasureRating(props: Readonly<SoftwareImpactMeasur
const rating = formatRating(value);
+ const additionalInfo =
+ SoftwareImpactRatingTooltip({
+ rating,
+ softwareQuality,
+ }) ?? undefined;
+
return (
- <Tooltip
- overlay={SoftwareImpactRatingTooltip({
- rating,
- softwareQuality,
- })}
- >
- <MetricsRatingBadge
- size="md"
- className="sw-text-sm"
- rating={rating}
- label={intl.formatMessage(
- {
- id: 'overview.project.software_impact.has_rating',
- },
- {
- softwareQuality: intl.formatMessage({ id: `software_quality.${softwareQuality}` }),
- rating,
- },
- )}
- />
- </Tooltip>
+ <>
+ <Tooltip content={additionalInfo}>
+ <MetricsRatingBadge
+ size="md"
+ className="sw-text-sm"
+ rating={rating}
+ label={intl.formatMessage(
+ {
+ id: 'overview.project.software_impact.has_rating',
+ },
+ {
+ softwareQuality: intl.formatMessage({ id: `software_quality.${softwareQuality}` }),
+ rating,
+ },
+ )}
+ />
+ </Tooltip>
+ {/* The badge is not interactive, so show the tooltip content for screen-readers only */}
+ <span className="sw-sr-only">{additionalInfo}</span>
+ </>
);
}
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx
index 6dc229906c6..1036ac37e3a 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx
@@ -22,11 +22,9 @@ import {
HelperHintIcon,
LightGreyCard,
LightLabel,
- PopupPlacement,
SnoozeCircleIcon,
TextError,
TextSubdued,
- Tooltip,
TrendDownCircleIcon,
TrendUpCircleIcon,
themeColor,
@@ -37,6 +35,7 @@ import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like';
import { formatMeasure } from '~sonar-aligned/helpers/measures';
import { getComponentIssuesUrl } from '~sonar-aligned/helpers/urls';
import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
+import Tooltip from '../../../components/controls/Tooltip';
import { getLeakValue } from '../../../components/measure/utils';
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import { findMeasure } from '../../../helpers/measures';
@@ -134,17 +133,19 @@ export default function IssueMeasuresCard(
<>
{intl.formatMessage({ id: 'overview.pull_request.fixed_issues' })}
<Tooltip
- overlay={
+ content={
<div className="sw-flex sw-flex-col sw-gap-4">
<span>
{intl.formatMessage({ id: 'overview.pull_request.fixed_issues.disclaimer' })}
</span>
<span>
- {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.disclaimer.2' })}
+ {intl.formatMessage({
+ id: 'overview.pull_request.fixed_issues.disclaimer.2',
+ })}
</span>
</div>
}
- placement={PopupPlacement.Top}
+ side="top"
>
<HelperHintIcon raised />
</Tooltip>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
index 92132eabcb1..49b037cc9f8 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.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 { Tooltip, TooltipSide } from '@sonarsource/echoes-react';
import {
ActionsDropdown,
ItemButton,
@@ -24,9 +25,7 @@ import {
ItemDivider,
ItemDownload,
ItemLink,
- PopupPlacement,
PopupZLevel,
- Tooltip,
} from 'design-system';
import { some } from 'lodash';
import * as React from 'react';
@@ -250,8 +249,8 @@ class ProfileActions extends React.PureComponent<Props, State> {
{actions.copy && (
<>
<Tooltip
- overlay={translateWithParameters('quality_profiles.extend_help', profile.name)}
- placement={PopupPlacement.Left}
+ content={translateWithParameters('quality_profiles.extend_help', profile.name)}
+ side={TooltipSide.Left}
>
<ItemButton
className="it__quality-profiles__extend"
@@ -262,8 +261,8 @@ class ProfileActions extends React.PureComponent<Props, State> {
</Tooltip>
<Tooltip
- overlay={translateWithParameters('quality_profiles.copy_help', profile.name)}
- placement={PopupPlacement.Left}
+ content={translateWithParameters('quality_profiles.copy_help', profile.name)}
+ side={TooltipSide.Left}
>
<ItemButton className="it__quality-profiles__copy" onClick={this.handleCopyClick}>
{translate('copy')}
@@ -281,8 +280,8 @@ class ProfileActions extends React.PureComponent<Props, State> {
{actions.setAsDefault &&
(hasNoActiveRules ? (
<Tooltip
- placement={PopupPlacement.Left}
- overlay={translate('quality_profiles.cannot_set_default_no_rules')}
+ content={translate('quality_profiles.cannot_set_default_no_rules')}
+ side={TooltipSide.Left}
>
<ItemButton
className="it__quality-profiles__set-as-default"
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
index 08309dd5457..672acc4d574 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
@@ -17,17 +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 {
- Badge,
- ButtonSecondary,
- ContentCell,
- Link,
- Spinner,
- SubTitle,
- Table,
- TableRow,
- Tooltip,
-} from 'design-system';
+import { Link, Spinner } from '@sonarsource/echoes-react';
+import { Badge, ButtonSecondary, ContentCell, SubTitle, Table, TableRow } from 'design-system';
import * as React from 'react';
import { getProfileProjects } from '../../../api/quality-profiles';
import ListFooter from '../../../components/controls/ListFooter';
@@ -191,21 +182,13 @@ export default class ProfileProjects extends React.PureComponent<Props, State> {
<SubTitle className="sw-mb-0">{translate('projects')}</SubTitle>
}
{profile.actions?.associateProjects && (
- <Tooltip
- overlay={
- hasNoActiveRules
- ? translate('quality_profiles.cannot_associate_projects_no_rules')
- : null
- }
+ <ButtonSecondary
+ className="it__quality-profiles__change-projects"
+ onClick={this.handleChangeClick}
+ disabled={hasNoActiveRules}
>
- <ButtonSecondary
- className="it__quality-profiles__change-projects"
- onClick={this.handleChangeClick}
- disabled={hasNoActiveRules}
- >
- {translate('quality_profiles.change_projects')}
- </ButtonSecondary>
- </Tooltip>
+ {translate('quality_profiles.change_projects')}
+ </ButtonSecondary>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationDetails.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationDetails.tsx
index 7d8308754e0..b2bd2d3948c 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationDetails.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationDetails.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 { Tooltip } from '@sonarsource/echoes-react';
import {
ButtonPrimary,
ButtonSecondary,
DangerButtonSecondary,
+ FlagMessage,
SubHeading,
- Tooltip,
} from 'design-system';
import React, { ReactElement } from 'react';
import { translate } from '../../../../helpers/l10n';
@@ -50,19 +51,20 @@ export default function ConfigurationDetails(props: Readonly<Props>) {
{title}
</SubHeading>
<p>{url}</p>
- <Tooltip
- overlay={!canDisable ? translate('settings.authentication.form.disable.tooltip') : null}
- >
- {enabled ? (
- <ButtonSecondary className="sw-mt-4" onClick={onToggle} disabled={!canDisable}>
- {translate('settings.authentication.form.disable')}
- </ButtonSecondary>
- ) : (
- <ButtonPrimary className="sw-mt-4" onClick={onToggle} disabled={!canDisable}>
- {translate('settings.authentication.form.enable')}
- </ButtonPrimary>
- )}
- </Tooltip>
+ {enabled ? (
+ <ButtonSecondary className="sw-mt-4" onClick={onToggle} disabled={!canDisable}>
+ {translate('settings.authentication.form.disable')}
+ </ButtonSecondary>
+ ) : (
+ <ButtonPrimary className="sw-mt-4" onClick={onToggle} disabled={!canDisable}>
+ {translate('settings.authentication.form.enable')}
+ </ButtonPrimary>
+ )}
+ {!canDisable && (
+ <FlagMessage className="sw-mt-2" variant="warning">
+ {translate('settings.authentication.form.disable.tooltip')}
+ </FlagMessage>
+ )}
</div>
<div className="sw-flex sw-gap-2 sw-flex-nowrap sw-shrink-0">
{extraActions}
@@ -70,7 +72,7 @@ export default function ConfigurationDetails(props: Readonly<Props>) {
{translate('settings.authentication.form.edit')}
</ButtonSecondary>
<Tooltip
- overlay={
+ content={
enabled || isDeleting ? translate('settings.authentication.form.delete.tooltip') : null
}
>
diff --git a/server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx b/server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx
index 22a28cdb0f2..c3f434223a0 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx
@@ -17,16 +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 {
- ActionCell,
- Avatar,
- ContentCell,
- InteractiveIcon,
- MenuIcon,
- Spinner,
- TableRow,
- Tooltip,
-} from 'design-system';
+import { IconMoreVertical, Spinner, Tooltip } from '@sonarsource/echoes-react';
+import { ActionCell, Avatar, ContentCell, InteractiveIcon, TableRow } from 'design-system';
import * as React from 'react';
import DateFromNow from '../../../components/intl/DateFromNow';
import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -85,12 +77,12 @@ export default function UserListItem(props: Readonly<UserListItemProps>) {
<DateFromNow date={sonarLintLastConnectionDate ?? ''} hourPrecision />
</ContentCell>
<ContentCell>
- <Spinner loading={groupsAreLoading}>
+ <Spinner isLoading={groupsAreLoading}>
{groupsCount}
{manageProvider === undefined && (
- <Tooltip overlay={translate('users.update_groups')}>
+ <Tooltip content={translate('users.update_groups')}>
<InteractiveIcon
- Icon={MenuIcon}
+ Icon={IconMoreVertical}
className="it__user-groups sw-ml-2"
aria-label={translateWithParameters('users.update_users_groups', user.login)}
onClick={() => setOpenGroupForm(true)}
@@ -101,11 +93,11 @@ export default function UserListItem(props: Readonly<UserListItemProps>) {
</Spinner>
</ContentCell>
<ContentCell>
- <Spinner loading={tokensAreLoading}>
+ <Spinner isLoading={tokensAreLoading}>
{tokens?.length}
- <Tooltip overlay={translateWithParameters('users.update_tokens')}>
+ <Tooltip content={translateWithParameters('users.update_tokens')}>
<InteractiveIcon
- Icon={MenuIcon}
+ Icon={IconMoreVertical}
className="it__user-tokens sw-ml-2"
aria-label={translateWithParameters('users.update_tokens_for_x', name ?? login)}
onClick={() => setOpenTokenForm(true)}
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.css b/server/sonar-web/src/main/js/components/controls/Tooltip.css
index 413629c903a..343208ac95c 100644
--- a/server/sonar-web/src/main/js/components/controls/Tooltip.css
+++ b/server/sonar-web/src/main/js/components/controls/Tooltip.css
@@ -50,15 +50,16 @@
}
.tooltip-inner {
+ font: var(--echoes-typography-paragraph-small-regular);
max-width: 22rem;
text-align: left;
text-decoration: none;
- border-radius: 8px;
+ border-radius: var(--echoes-border-radius-200);
overflow: hidden;
word-break: break-word;
- padding: 12px 17px;
- color: #eff2f9;
- background-color: #2a2f40;
+ padding: var(--echoes-dimension-space-50) var(--echoes-dimension-space-150);
+ color: var(--echoes-color-text-on-color);
+ background-color: var(--echoes-color-background-inverse);
}
.tooltip-inner .alert {
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
index a9b2ab472de..56e463fb7f1 100644
--- a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
+++ b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
@@ -31,7 +31,7 @@ import './Tooltip.css';
export type Placement = 'bottom' | 'right' | 'left' | 'top';
-export interface TooltipProps {
+interface TooltipProps {
classNameSpace?: string;
children: React.ReactElement;
mouseEnterDelay?: number;
@@ -74,6 +74,19 @@ function isMeasured(state: State): state is OwnState & Measurements {
return state.height !== undefined;
}
+/** @deprecated Use {@link Echoes.Tooltip | Tooltip} from Echoes instead.
+ *
+ * Echoes Tooltip component should mainly be used on interactive element and contain very simple text based content.
+ * If the content is more complex use a Popover component instead (not available yet).
+ *
+ * Some of the props have changed or been renamed:
+ * - `children` is the trigger for the tooltip, should be an interactive Element. If not an Echoes component, make sure the component forwards the props and the ref to an interactive DOM node, it's needed by the tooltip to position itself.
+ * - `overlay` is now `content`, that's the tooltip content. It's a ReactNode for convenience but should render only text based content, no interactivity is allowed inside the tooltip.
+ * - ~`mouseEnterDelay`~ doesn't exist anymore, was mostly used in situation that should be replaced by a Popover component.
+ * - ~`mouseLeaveDelay`~ doesn't exist anymore, was mostly used in situation that should be replaced by a Popover component.
+ * - `placement` is now `align` and `side`, based on the {@link Echoes.TooltipAlign | TooltipAlign} and {@link Echoes.TooltipSide | TooltipSide} enums.
+ * - `visible` is now `isOpen`
+ */
export default function Tooltip(props: TooltipProps) {
// `overlay` is a ReactNode, so it can be `undefined` or `null`. This allows to easily
// render a tooltip conditionally. More generally, we avoid rendering empty tooltips.
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx
index 81cab9095c3..3777c8b68ad 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx
@@ -17,18 +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 {
- HelperHintIcon,
- ItemButton,
- PageContentFontWrapper,
- PopupPlacement,
- TextBold,
- TextMuted,
- Tooltip,
-} from 'design-system';
+import { IconQuestionMark } from '@sonarsource/echoes-react';
+import { ItemButton, PageContentFontWrapper, TextBold, TextMuted } from 'design-system';
import * as React from 'react';
import { useIntl } from 'react-intl';
import { translate } from '../../../helpers/l10n';
+import HelpTooltip from '../../../sonar-aligned/components/controls/HelpTooltip';
import { IssueTransition } from '../../../types/issues';
type Props = {
@@ -68,9 +62,9 @@ export function IssueTransitionItem({ transition, selected, onSelectTransition }
<PageContentFontWrapper className="sw-font-semibold sw-flex sw-gap-1 sw-items-center">
<TextBold name={intl.formatMessage({ id: `issue.transition.${transition}` })} />
{tooltips[transition] && (
- <Tooltip overlay={<div>{tooltips[transition]}</div>} placement={PopupPlacement.Right}>
- <HelperHintIcon />
- </Tooltip>
+ <HelpTooltip overlay={tooltips[transition]} placement="right">
+ <IconQuestionMark />
+ </HelpTooltip>
)}
</PageContentFontWrapper>
<TextMuted text={translate('issue.transition', transition, 'description')} />
diff --git a/server/sonar-web/src/main/js/components/tags/TagsList.css b/server/sonar-web/src/main/js/components/tags/TagsList.css
deleted file mode 100644
index 2d6c7b6b6fc..00000000000
--- a/server/sonar-web/src/main/js/components/tags/TagsList.css
+++ /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.
- */
-.tags-list {
- white-space: nowrap;
- line-height: 16px;
-}
-
-.tags-list i::before {
- font-size: var(--smallFontSize);
-}
-
-.tags-list span {
- display: inline-block;
- vertical-align: middle;
- text-align: left;
- max-width: 220px;
- padding-left: 4px;
- padding-right: 4px;
-}
diff --git a/server/sonar-web/src/main/js/components/tags/TagsList.tsx b/server/sonar-web/src/main/js/components/tags/TagsList.tsx
index 6c75f6a5978..a24db5de119 100644
--- a/server/sonar-web/src/main/js/components/tags/TagsList.tsx
+++ b/server/sonar-web/src/main/js/components/tags/TagsList.tsx
@@ -17,10 +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 { PopupPlacement, Tags, Tooltip } from 'design-system';
+import { PopupPlacement, Tags } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../helpers/l10n';
-import './TagsList.css';
+import Tooltip from '../controls/Tooltip';
interface Props {
allowUpdate?: boolean;
diff --git a/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx b/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
index 2faaba061de..e4eb8400ac5 100644
--- a/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
+++ b/server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { TooltipProvider } from '@sonarsource/echoes-react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Matcher, RenderResult, render, screen, within } from '@testing-library/react';
import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
@@ -126,11 +127,13 @@ export function renderComponent(
<AvailableFeaturesContext.Provider value={featureList}>
<CurrentUserContextProvider currentUser={currentUser}>
<AppStateContextProvider appState={appState}>
- <MemoryRouter initialEntries={[pathname]}>
- <Routes>
- <Route path="*" element={children} />
- </Routes>
- </MemoryRouter>
+ <TooltipProvider delayDuration={0}>
+ <MemoryRouter initialEntries={[pathname]}>
+ <Routes>
+ <Route path="*" element={children} />
+ </Routes>
+ </MemoryRouter>
+ </TooltipProvider>
</AppStateContextProvider>
</CurrentUserContextProvider>
</AvailableFeaturesContext.Provider>
@@ -238,7 +241,9 @@ function renderRoutedApp(
<QueryClientProvider client={queryClient}>
<ToastMessageContainer />
- <RouterProvider router={router} />
+ <TooltipProvider delayDuration={0}>
+ <RouterProvider router={router} />
+ </TooltipProvider>
</QueryClientProvider>
</IndexationContextProvider>
</AppStateContextProvider>
diff --git a/server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx b/server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx
index b36e8009bd5..0ab20b45acf 100644
--- a/server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx
+++ b/server/sonar-web/src/main/js/sonar-aligned/components/measure/Measure.tsx
@@ -17,8 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Tooltip } from '@sonarsource/echoes-react';
import classNames from 'classnames';
-import { MetricsRatingBadge, QualityGateIndicator, RatingLabel, Tooltip } from 'design-system';
+import { MetricsRatingBadge, QualityGateIndicator, RatingLabel } from 'design-system';
import React from 'react';
import { useIntl } from 'react-intl';
import { formatMeasure } from '~sonar-aligned/helpers/measures';
@@ -105,8 +106,11 @@ export default function Measure({
);
return (
- <Tooltip overlay={tooltip}>
- <span className={className}>{rating}</span>
+ <Tooltip content={tooltip}>
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
+ <span className={className} tabIndex={0}>
+ {rating}
+ </span>
</Tooltip>
);
}