From 1953626fabdb68e2f271899a16ad30faf4c2a7c8 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 21 May 2024 17:05:03 +0200 Subject: [PATCH] SONAR-22218 Deprecate old tooltips and migrate some of them --- .../config/jest/SetupReactTestingLibrary.ts | 10 +- .../src/components/DropdownMenu.tsx | 38 ++++--- .../src/components/InteractiveIcon.tsx | 96 +++++++--------- .../design-system/src/components/Tooltip.tsx | 26 ++++- .../design-system/src/components/index.ts | 1 - .../components/MetricsRatingBadge.tsx | 49 ++++---- .../components/buttons/Button.tsx | 105 +++++++++--------- .../src/main/js/app/utils/startReactApp.tsx | 17 ++- .../issues/components/IssueHeaderMeta.tsx | 7 +- .../SoftwareImpactMeasureBreakdownCard.tsx | 5 +- .../branches/SoftwareImpactMeasureCard.tsx | 5 +- .../branches/SoftwareImpactMeasureRating.tsx | 50 +++++---- .../pullRequests/IssueMeasuresCard.tsx | 11 +- .../components/ProfileActions.tsx | 15 ++- .../details/ProfileProjects.tsx | 33 ++---- .../authentication/ConfigurationDetails.tsx | 32 +++--- .../js/apps/users/components/UserListItem.tsx | 24 ++-- .../main/js/components/controls/Tooltip.css | 9 +- .../main/js/components/controls/Tooltip.tsx | 15 ++- .../issue/components/IssueTransitionItem.tsx | 18 +-- .../src/main/js/components/tags/TagsList.css | 36 ------ .../src/main/js/components/tags/TagsList.tsx | 4 +- .../main/js/helpers/testReactTestingUtils.tsx | 17 ++- .../components/measure/Measure.tsx | 10 +- 24 files changed, 306 insertions(+), 327 deletions(-) delete mode 100644 server/sonar-web/src/main/js/components/tags/TagsList.css 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; } -export function ItemButton(props: ItemButtonProps) { - const { children, className, disabled, icon, innerRef, onClick, selected, ...liProps } = props; - return ( -
  • - - {icon} - {children} - -
  • - ); -} +export const ItemButton = forwardRef( + (props: ItemButtonProps, ref: ForwardedRef) => { + const { children, className, disabled, icon, innerRef, onClick, selected, ...liProps } = props; + return ( +
  • + + {icon} + {children} + +
  • + ); + }, +); +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; - onClick?: VoidFunction; + onClick?: (event: MouseEvent) => void; size?: InteractiveIconSize; stopPropagation?: boolean; - to?: LinkProps['to']; } -export class InteractiveIconBase extends React.PureComponent { - handleClick = (event: React.MouseEvent) => { - const { disabled, onClick, stopPropagation = true } = this.props; - - if (stopPropagation) { - event.stopPropagation(); - } - - if (onClick && !disabled) { - onClick(); - } - }; - - render() { +export const InteractiveIconBase = forwardRef( + (props: InteractiveIconProps, ref: ForwardedRef) => { const { Icon, children, disabled, - innerRef, onClick, size = 'medium', - to, iconProps = {}, + stopPropagation = true, ...htmlProps - } = this.props; + } = props; + + const handleClick = useCallback( + (event: React.MouseEvent) => { + 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 ( - - - {children} - - ); - } - return ( - + {children} ); - } -} + }, +); + +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> = 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> = - styled(InteractiveIcon)` - --color: ${themeColor('discreetInteractiveIcon')}; - `; +export const DiscreetInteractiveIcon = styled(InteractiveIcon)` + --color: ${themeColor('discreetInteractiveIcon')}; +`; -export const DestructiveIcon: React.FC> = 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> = - 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) { - if (!rating) { +export const MetricsRatingBadge = forwardRef( + ({ className, size = 'sm', label, rating, ...ariaAttrs }: Readonly, ref) => { + if (!rating) { + return ( + + — + + ); + } return ( - - — - + {rating} + ); - } - return ( - - {rating} - - ); -} + }, +); + +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; + icon?: ReactNode; isExternal?: LinkProps['isExternal']; + onClick?: (event: MouseEvent) => unknown; - onClick?: (event: React.MouseEvent) => 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 { - handleClick = (event: React.MouseEvent) => { - 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((props, ref) => { + const { + children, + disabled, + icon, + onClick, + preventDefault = props.type !== 'submit', + stopPropagation = false, + to, + type = 'button', + ...htmlProps + } = props; + + const handleClick = useCallback( + (event: MouseEvent) => { + 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 ( - - {icon} - {children} - - ); - } - + if (to) { return ( - + {icon} {children} - + ); } -} + + return ( + + {icon} + {children} + + ); +}); +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( - + + + + + @@ -285,3 +291,12 @@ export default function startReactApp( , ); } + +/* + * 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) { - {!!issue.codeVariants?.length && ( + {(issue.codeVariants?.length ?? 0) > 0 && ( <>
    {translate('issue.code_variants')} - + {issue.codeVariants?.join(', ')} 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 ( 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
    {count ? ( - + - - + <> + + + + {/* The badge is not interactive, so show the tooltip content for screen-readers only */} + {additionalInfo} + ); } 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' })} {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.disclaimer' })} - {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.disclaimer.2' })} + {intl.formatMessage({ + id: 'overview.pull_request.fixed_issues.disclaimer.2', + })}
    } - placement={PopupPlacement.Top} + side="top" >
    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 { {actions.copy && ( <> { {translate('copy')} @@ -281,8 +280,8 @@ class ProfileActions extends React.PureComponent { {actions.setAsDefault && (hasNoActiveRules ? ( { {translate('projects')} } {profile.actions?.associateProjects && ( - - - {translate('quality_profiles.change_projects')} - - + {translate('quality_profiles.change_projects')} + )}
    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) { {title}

    {url}

    - - {enabled ? ( - - {translate('settings.authentication.form.disable')} - - ) : ( - - {translate('settings.authentication.form.enable')} - - )} - + {enabled ? ( + + {translate('settings.authentication.form.disable')} + + ) : ( + + {translate('settings.authentication.form.enable')} + + )} + {!canDisable && ( + + {translate('settings.authentication.form.disable.tooltip')} + + )}
    {extraActions} @@ -70,7 +72,7 @@ export default function ConfigurationDetails(props: Readonly) { {translate('settings.authentication.form.edit')} 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) { - + {groupsCount} {manageProvider === undefined && ( - + setOpenGroupForm(true)} @@ -101,11 +93,11 @@ export default function UserListItem(props: Readonly) { - + {tokens?.length} - + 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 } {tooltips[transition] && ( - {tooltips[transition]}
    } placement={PopupPlacement.Right}> - - + + + )} 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( - - - - - + + + + + + + @@ -238,7 +241,9 @@ function renderRoutedApp( - + + + 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 ( - - {rating} + + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} + + {rating} + ); } -- 2.39.5