aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2024-07-22 11:25:25 +0200
committersonartech <sonartech@sonarsource.com>2024-07-24 20:02:49 +0000
commit87c9b7fad2fdf0672997c4e27d15dc3606931535 (patch)
tree280133dc1b355569f9c5f60d850ea121f1df2ee8 /server/sonar-web
parent04de2de3d71fceab34404f5bb047671658dd1bb9 (diff)
downloadsonarqube-87c9b7fad2fdf0672997c4e27d15dc3606931535.tar.gz
sonarqube-87c9b7fad2fdf0672997c4e27d15dc3606931535.zip
SONAR-22524 Replace InteractiveIcons with ButtonIcon
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/design-system/src/components/InteractiveIcon.tsx67
-rw-r--r--server/sonar-web/design-system/src/components/input/InputSearch.tsx4
-rw-r--r--server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts11
-rw-r--r--server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/PromotedSection.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UserActions.tsx77
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx47
-rw-r--r--server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopupHelper.tsx23
14 files changed, 220 insertions, 143 deletions
diff --git a/server/sonar-web/design-system/src/components/InteractiveIcon.tsx b/server/sonar-web/design-system/src/components/InteractiveIcon.tsx
index 9458645fa21..f5bc0f996f1 100644
--- a/server/sonar-web/design-system/src/components/InteractiveIcon.tsx
+++ b/server/sonar-web/design-system/src/components/InteractiveIcon.tsx
@@ -135,6 +135,25 @@ const IconButton = styled.button`
${buttonIconStyle}
`;
+/**
+ * @deprecated Use ButtonIcon from Echoes instead.
+ *
+ * Use the `variety` prop with the ButtonVariety enum to change the
+ * button's look and feel.
+ *
+ * Some of the props have changed or been renamed:
+ * - `disabled` is now `isDisabled`, note that an Echoes Tooltip won't work
+ * on a disabled button, use a text notice or ToggleTip next to the disabled button instead.
+ * - `Icon` is restricted to Echoes' Icons
+ * - `aria-label` is now `ariaLabel`
+ * - `size` now requires a value from the ButtonSize enum
+ *
+ * New props:
+ * - `tooltipContent` overrides the content of the tooltip (which defaults to the value of ariaLabel!)
+ * - `tooltipProps` allows you to customize the tooltip positioning (`align` and `side`)
+ *
+ * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3382706231/Button | Migration Guide} for more information.
+ */
export const InteractiveIcon = styled(InteractiveIconBase)`
--background: ${themeColor('interactiveIcon')};
--backgroundHover: ${themeColor('interactiveIconHover')};
@@ -144,10 +163,42 @@ export const InteractiveIcon = styled(InteractiveIconBase)`
--focus: ${themeColor('interactiveIconFocus', OPACITY_20_PERCENT)};
`;
+/**
+ * @deprecated Use ButtonIcon from Echoes instead, with the ButtonVariety.DefaultGhost variety.
+ *
+ * Some of the props have changed or been renamed:
+ * - `disabled` is now `isDisabled`, note that an Echoes Tooltip won't work
+ * on a disabled button, use a text notice or ToggleTip next to the disabled button instead.
+ * - `Icon` is restricted to Echoes' Icons
+ * - `aria-label` is now `ariaLabel`
+ * - `size` now requires a value from the ButtonSize enum
+ *
+ * New props:
+ * - `tooltipContent` overrides the content of the tooltip (which defaults to the value of ariaLabel!)
+ * - `tooltipProps` allows you to customize the tooltip positioning (`align` and `side`)
+ *
+ * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3382706231/Button | Migration Guide} for more information.
+ */
export const DiscreetInteractiveIcon = styled(InteractiveIcon)`
--color: ${themeColor('discreetInteractiveIcon')};
`;
+/**
+ * @deprecated Use ButtonIcon from Echoes instead, with the ButtonVariety.DangerGhost variety.
+ *
+ * Some of the props have changed or been renamed:
+ * - `disabled` is now `isDisabled`, note that an Echoes Tooltip won't work
+ * on a disabled button, use a text notice or ToggleTip next to the disabled button instead.
+ * - `Icon` is restricted to Echoes' Icons
+ * - `aria-label` is now `ariaLabel`
+ * - `size` now requires a value from the ButtonSize enum
+ *
+ * New props:
+ * - `tooltipContent` overrides the content of the tooltip (which defaults to the value of ariaLabel!)
+ * - `tooltipProps` allows you to customize the tooltip positioning (`align` and `side`)
+ *
+ * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3382706231/Button | Migration Guide} for more information.
+ */
export const DestructiveIcon = styled(InteractiveIconBase)`
--background: ${themeColor('destructiveIcon')};
--backgroundHover: ${themeColor('destructiveIconHover')};
@@ -156,6 +207,22 @@ export const DestructiveIcon = styled(InteractiveIconBase)`
--focus: ${themeColor('destructiveIconFocus', OPACITY_20_PERCENT)};
`;
+/**
+ * @deprecated Use ButtonIcon from Echoes instead, with the ButtonVariety.DefaultGhost variety.
+ *
+ * Some of the props have changed or been renamed:
+ * - `disabled` is now `isDisabled`, note that an Echoes Tooltip won't work
+ * on a disabled button, use a text notice or ToggleTip next to the disabled button instead.
+ * - `Icon` is restricted to Echoes' Icons
+ * - `aria-label` is now `ariaLabel`
+ * - `size` now requires a value from the ButtonSize enum
+ *
+ * New props:
+ * - `tooltipContent` overrides the content of the tooltip (which defaults to the value of ariaLabel!)
+ * - `tooltipProps` allows you to customize the tooltip positioning (`align` and `side`)
+ *
+ * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3382706231/Button | Migration Guide} for more information.
+ */
export const DismissProductNewsIcon = styled(InteractiveIcon)`
--background: ${themeColor('productNews')};
--backgroundHover: ${themeColor('productNewsHover')};
diff --git a/server/sonar-web/design-system/src/components/input/InputSearch.tsx b/server/sonar-web/design-system/src/components/input/InputSearch.tsx
index 6880e047c49..8e88c655fc1 100644
--- a/server/sonar-web/design-system/src/components/input/InputSearch.tsx
+++ b/server/sonar-web/design-system/src/components/input/InputSearch.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
+import { IconSearch } from '@sonarsource/echoes-react';
import classNames from 'classnames';
import { debounce } from 'lodash';
import React, { PropsWithChildren, useEffect, useMemo, useRef, useState } from 'react';
@@ -31,7 +32,6 @@ import { InputSizeKeys } from '../../types/theme';
import { InteractiveIcon } from '../InteractiveIcon';
import { Spinner } from '../Spinner';
import { CloseIcon } from '../icons/CloseIcon';
-import { SearchIcon } from '../icons/SearchIcon';
interface Props {
autoFocus?: boolean;
@@ -238,7 +238,7 @@ export const StyledInputWrapper = styled.div`
}
`;
-export const StyledSearchIcon = styled(SearchIcon)`
+export const StyledSearchIcon = styled(IconSearch)`
color: ${themeColor('inputBorder')};
`;
diff --git a/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts
index 33849598d1f..518a5cb3e31 100644
--- a/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts
@@ -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 { cloneDeep, uniqueId } from 'lodash';
import { Visibility } from '~sonar-aligned/types/component';
import {
@@ -64,6 +65,14 @@ import {
setupGitlabProjectCreation,
} from '../alm-integrations';
+let uniqueNumber = 0;
+
+function createUniqueNumber() {
+ uniqueNumber += 1;
+
+ return uniqueNumber;
+}
+
export default class AlmIntegrationsServiceMock {
almInstancePATMap: { [key: string]: boolean } = {};
gitlabProjects: GitlabProject[];
@@ -301,7 +310,7 @@ export default class AlmIntegrationsServiceMock {
const generatedRepositories = Array.from(Array(quantity).keys()).map((index) => {
return mockBitbucketCloudRepository({
name: `Gitlab project ${index}`,
- uuid: Math.floor(Math.random() * 100000),
+ uuid: createUniqueNumber(),
});
});
diff --git a/server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx b/server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx
index 22f2a8856b3..99969f28c53 100644
--- a/server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx
+++ b/server/sonar-web/src/main/js/app/components/global-search/GlobalSearch.tsx
@@ -17,17 +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 {
- DropdownMenu,
- INTERACTIVE_TOOLTIP_DELAY,
- InputSearch,
- InteractiveIcon,
- MenuSearchIcon,
- Popup,
- PopupZLevel,
- TextMuted,
-} from 'design-system';
-import { debounce, uniqBy } from 'lodash';
+import { ButtonIcon, ButtonVariety, IconSearch } from '@sonarsource/echoes-react';
+import { DropdownMenu, InputSearch, Popup, PopupZLevel, TextMuted } from 'design-system';
+import { debounce, isEmpty, uniqBy } from 'lodash';
import * as React from 'react';
import { withRouter } from '~sonar-aligned/components/hoc/withRouter';
import { ComponentQualifier } from '~sonar-aligned/types/component';
@@ -35,7 +27,6 @@ import { Router } from '~sonar-aligned/types/router';
import { getSuggestions } from '../../../api/components';
import FocusOutHandler from '../../../components/controls/FocusOutHandler';
import OutsideClickHandler from '../../../components/controls/OutsideClickHandler';
-import Tooltip from '../../../components/controls/Tooltip';
import { PopupPlacement } from '../../../components/ui/popups';
import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
import { KeyboardKeys } from '../../../helpers/keycodes';
@@ -419,17 +410,14 @@ export class GlobalSearch extends React.PureComponent<Props, State> {
return (
<form role="search">
- {!open && !query ? (
- <Tooltip mouseEnterDelay={INTERACTIVE_TOOLTIP_DELAY} content={translate('search_verb')}>
- <InteractiveIcon
- className="it__search-icon"
- Icon={MenuSearchIcon}
- aria-label={translate('search_verb')}
- currentColor
- onClick={this.handleFocus}
- size="medium"
- />
- </Tooltip>
+ {!open && isEmpty(query) ? (
+ <ButtonIcon
+ Icon={IconSearch}
+ ariaLabel={translate('search_verb')}
+ className="it__search-icon"
+ onClick={this.handleFocus}
+ variety={ButtonVariety.DefaultGhost}
+ />
) : (
<FocusOutHandler onFocusOut={this.handleClickOutside}>
<OutsideClickHandler onClickOutside={this.handleClickOutside}>
diff --git a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts
index 5ad77477c21..e19565fd7c4 100644
--- a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts
+++ b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts
@@ -85,7 +85,7 @@ it('should allow navigating through the tree', async () => {
// Navigate by clicking on an element.
await ui.clickOnChildComponent(/folderA$/);
- expect(await ui.childComponent(/out\.tsx/).findAll()).toHaveLength(2); // One for the pin, one for the name column
+ expect(await ui.childComponent(/out\.tsx/).find()).toBeInTheDocument();
expect(screen.getByRole('navigation', { name: 'breadcrumbs' })).toBeInTheDocument();
// Navigate back using the breadcrumb.
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
index 84969ad834d..ec687017856 100644
--- a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
@@ -17,7 +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 { InteractiveIcon, PinIcon } from 'design-system';
+import { ButtonIcon, ButtonVariety, IconPin } from '@sonarsource/echoes-react';
import * as React from 'react';
import { WorkspaceContextShape } from '../../../components/workspace/context';
import { translateWithParameters } from '../../../helpers/l10n';
@@ -42,11 +42,12 @@ export default function ComponentPin(props: Props) {
});
}, [branchLike, component, openComponent]);
- const label = translateWithParameters('component_viewer.open_in_workspace_X', component.name);
-
return (
- <span title={label}>
- <InteractiveIcon aria-label={label} Icon={PinIcon} onClick={handleClick} />
- </span>
+ <ButtonIcon
+ ariaLabel={translateWithParameters('component_viewer.open_in_workspace_X', component.name)}
+ Icon={IconPin}
+ onClick={handleClick}
+ variety={ButtonVariety.PrimaryGhost}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx b/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx
index 43e1b37568f..57ae8afee50 100644
--- a/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx
@@ -18,12 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonIcon, ButtonSize, ButtonVariety, IconX } from '@sonarsource/echoes-react';
import {
ButtonPrimary,
ButtonSecondary,
- CloseIcon,
FlagMessage,
- InteractiveIcon,
Link,
Spinner,
Title,
@@ -193,12 +192,12 @@ export default function NewCodeDefinitionSelection(props: Props) {
id="onboarding.create_project.manual.step2"
defaultMessage={translate('onboarding.create_project.manual.step2')}
/>
- <InteractiveIcon
- Icon={CloseIcon}
- aria-label={intl.formatMessage({ id: 'clear' })}
- currentColor
+ <ButtonIcon
+ Icon={IconX}
+ ariaLabel={intl.formatMessage({ id: 'clear' })}
onClick={onClose}
- size="small"
+ size={ButtonSize.Medium}
+ variety={ButtonVariety.DefaultGhost}
/>
</div>
<Title>
diff --git a/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx
index 71e9bcdcecd..5c144e525cf 100644
--- a/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx
@@ -18,17 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonIcon, ButtonSize, ButtonVariety, IconX } from '@sonarsource/echoes-react';
import classNames from 'classnames';
import {
ButtonPrimary,
ButtonSecondary,
- CloseIcon,
FlagErrorIcon,
FlagMessage,
FlagSuccessIcon,
FormField,
InputField,
- InteractiveIcon,
Link,
Note,
Title,
@@ -146,12 +145,12 @@ export default function ManualProjectCreate(props: Readonly<Props>) {
id="onboarding.create_project.manual.step1"
defaultMessage={translate('onboarding.create_project.manual.step1')}
/>
- <InteractiveIcon
- Icon={CloseIcon}
- aria-label={intl.formatMessage({ id: 'clear' })}
- currentColor
+ <ButtonIcon
+ Icon={IconX}
+ ariaLabel={intl.formatMessage({ id: 'clear' })}
onClick={props.onClose}
- size="small"
+ size={ButtonSize.Medium}
+ variety={ButtonVariety.DefaultGhost}
/>
</div>
<Title>{translate('onboarding.create_project.manual.title')}</Title>
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
index 2f1bf24ba55..0d7464ba00a 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
@@ -19,16 +19,15 @@
*/
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
+import { ButtonIcon, ButtonVariety, IconUnfold } from '@sonarsource/echoes-react';
import classNames from 'classnames';
import {
ChevronRightIcon,
ClipboardIconButton,
HoverLink,
- InteractiveIcon,
LightLabel,
Link,
Spinner,
- UnfoldIcon,
themeColor,
} from 'design-system';
import * as React from 'react';
@@ -180,11 +179,11 @@ export function IssueSourceViewerHeader(props: Readonly<Props>) {
{expandable && !(loading ?? isLoadingBranches) && (
<div className="sw-ml-4">
- <InteractiveIcon
- Icon={UnfoldIcon}
- aria-label={translate('source_viewer.expand_all_lines')}
- className="sw-h-6"
+ <ButtonIcon
+ Icon={IconUnfold}
+ ariaLabel={translate('source_viewer.expand_all_lines')}
onClick={onExpand}
+ variety={ButtonVariety.PrimaryGhost}
/>
</div>
)}
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/PromotedSection.tsx b/server/sonar-web/src/main/js/apps/overview/branches/PromotedSection.tsx
index 11e27b6e3a6..212e1691027 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/PromotedSection.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/PromotedSection.tsx
@@ -18,14 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
-import { IconX } from '@sonarsource/echoes-react';
-import {
- ButtonPrimary,
- ButtonSecondary,
- InteractiveIcon,
- themeBorder,
- themeColor,
-} from 'design-system';
+import { ButtonIcon, ButtonSize, ButtonVariety, IconX } from '@sonarsource/echoes-react';
+import { ButtonPrimary, ButtonSecondary, themeBorder, themeColor } from 'design-system';
import React, { useState } from 'react';
import { translate } from '../../../helpers/l10n';
@@ -68,11 +62,13 @@ export default function PromotedSection({
<StyledWrapper className="sw-p-4 sw-pl-6 sw-my-6 sw-rounded-2">
<div className="sw-flex sw-justify-between sw-mb-2">
<StyledTitle className="sw-body-md-highlight">{title}</StyledTitle>
- <InteractiveIcon
+
+ <ButtonIcon
Icon={IconX}
- aria-label={translate('dismiss')}
+ ariaLabel={translate('dismiss')}
onClick={handleDismiss}
- size="small"
+ size={ButtonSize.Medium}
+ variety={ButtonVariety.DefaultGhost}
/>
</div>
<p className="sw-body-sm sw-mb-4">{content}</p>
diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx
index f519d9c4329..8061b5fc720 100644
--- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx
@@ -66,7 +66,7 @@ const ui = {
aliceUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.alice.merveille' }),
aliceUpdateButton: byRole('button', { name: 'users.manage_user.alice.merveille' }),
denisUpdateButton: byRole('button', { name: 'users.manage_user.denis.villeneuve' }),
- alicedDeactivateButton: byRole('menuitem', { name: 'users.deactivate' }),
+ alicedDeactivateButton: byText('users.deactivate'),
bobUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.bob.marley' }),
bobUpdateButton: byRole('button', { name: 'users.manage_user.bob.marley' }),
scmAddButton: byRole('button', { name: 'add_verb' }),
@@ -374,7 +374,7 @@ describe('in non managed mode', () => {
renderUsersApp();
await user.click(await ui.aliceUpdateButton.find());
- await user.click(await ui.aliceRow.byRole('menuitem', { name: 'update_details' }).find());
+ await user.click(await byText('update_details').find());
expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
expect(ui.userNameInput.get()).toHaveValue('Alice Merveille');
@@ -393,7 +393,7 @@ describe('in non managed mode', () => {
renderUsersApp();
await user.click(await ui.aliceUpdateButton.find());
- await user.click(await ui.aliceRow.byRole('menuitem', { name: 'users.deactivate' }).find());
+ await user.click(await byText('users.deactivate').find());
expect(await ui.dialogDeactivateUser.find()).toBeInTheDocument();
expect(ui.deleteUserAlert.query()).not.toBeInTheDocument();
await user.click(ui.deleteUserCheckbox.get());
@@ -410,9 +410,7 @@ describe('in non managed mode', () => {
renderUsersApp([], currentUser);
await user.click(await ui.aliceUpdateButton.find());
- await user.click(
- await ui.aliceRow.byRole('menuitem', { name: 'my_profile.password.title' }).find(),
- );
+ await user.click(await byText('my_profile.password.title').find());
expect(await ui.dialogPasswords.find()).toBeInTheDocument();
expect(await ui.oldPassword.find()).toBeInTheDocument();
@@ -463,7 +461,7 @@ describe('in non managed mode', () => {
renderUsersApp([], currentUser);
await user.click(await ui.denisUpdateButton.find());
- await user.click(await ui.denisRow.byRole('menuitem', { name: 'update_details' }).find());
+ await user.click(await byText('update_details').find());
expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
expect(ui.userNameInput.get()).toHaveValue('Denis Villeneuve');
@@ -483,7 +481,7 @@ describe('in non managed mode', () => {
expect(await ui.aliceRow.byText('alice.merveille@wonderland.com').find()).toBeInTheDocument();
await user.click(await ui.aliceUpdateButton.find());
- await user.click(await ui.aliceRow.byRole('menuitem', { name: 'update_details' }).find());
+ await user.click(await byText('update_details').find());
expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
expect(ui.emailInput.get()).toHaveValue('alice.merveille@wonderland.com');
@@ -534,7 +532,7 @@ describe('in manage mode', () => {
ui.bobRow.byRole('button', { name: 'my_profile.password.title' }).query(),
).not.toBeInTheDocument();
- await user.click(ui.bobRow.byRole('menuitem', { name: 'update_scm' }).get());
+ await user.click(byText('update_scm').get());
expect(ui.userNameInput.get()).toBeDisabled();
expect(ui.emailInput.get()).toBeDisabled();
@@ -744,9 +742,11 @@ it('accessibility', async () => {
// user update dialog should be accessible
await user.click(await ui.aliceUpdateButton.find());
- await user.click(await ui.aliceRow.byRole('menuitem', { name: 'update_details' }).find());
+ await user.click(await byText('update_details').find());
expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
- await expect(await ui.dialogUpdateUser.find()).toHaveNoA11yViolations();
+ await waitFor(async () => {
+ await expect(await ui.dialogUpdateUser.find()).toHaveNoA11yViolations();
+ });
await user.click(ui.cancelButton.get());
// user tokens dialog should be accessible
@@ -766,11 +766,11 @@ it('accessibility', async () => {
// user password dialog should be accessible
await user.click(await ui.aliceUpdateButton.find());
- await user.click(
- await ui.aliceRow.byRole('menuitem', { name: 'my_profile.password.title' }).find(),
- );
+ await user.click(await byText('my_profile.password.title').find());
expect(await ui.dialogPasswords.find()).toBeInTheDocument();
- await expect(await ui.dialogPasswords.find()).toHaveNoA11yViolations();
+ await waitFor(async () => {
+ await expect(await ui.dialogPasswords.find()).toHaveNoA11yViolations();
+ });
});
function renderUsersApp(featureList: Feature[] = [], currentUser?: CurrentUser) {
diff --git a/server/sonar-web/src/main/js/apps/users/components/UserActions.tsx b/server/sonar-web/src/main/js/apps/users/components/UserActions.tsx
index 59a775528c1..6664eb9b1d7 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UserActions.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/UserActions.tsx
@@ -19,12 +19,11 @@
*/
import {
- ActionsDropdown,
- ItemButton,
- ItemDangerButton,
- ItemDivider,
- PopupZLevel,
-} from 'design-system';
+ ButtonIcon,
+ ButtonVariety,
+ DropdownMenu,
+ IconMoreVertical,
+} from '@sonarsource/echoes-react';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Provider } from '../../../types/types';
@@ -49,31 +48,49 @@ export default function UserActions(props: Props) {
return (
<>
- <ActionsDropdown
- id={`user-settings-action-dropdown-${user.login}`}
- toggleClassName="it__user-actions-toggle"
- allowResizing
- ariaLabel={translateWithParameters('users.manage_user', user.login)}
- zLevel={PopupZLevel.Global}
+ <DropdownMenu.Root
+ items={
+ <>
+ <DropdownMenu.ItemButton
+ className="it__user-update"
+ key="update"
+ onClick={() => setOpenForm('update')}
+ >
+ {isInstanceManaged ? translate('update_scm') : translate('update_details')}
+ </DropdownMenu.ItemButton>
+
+ {!isInstanceManaged && user.local && (
+ <DropdownMenu.ItemButton
+ className="it__user-change-password"
+ key="change_password"
+ onClick={() => setOpenForm('password')}
+ >
+ {translate('my_profile.password.title')}
+ </DropdownMenu.ItemButton>
+ )}
+ {isUserActive(user) && !isInstanceManaged && <DropdownMenu.Separator key="separator" />}
+
+ {isUserActive(user) && (!isInstanceManaged || isUserLocal) && (
+ <DropdownMenu.ItemButtonDestructive
+ className="it__user-deactivate"
+ key="deactivate"
+ onClick={() => setOpenForm('deactivate')}
+ >
+ {translate('users.deactivate')}
+ </DropdownMenu.ItemButtonDestructive>
+ )}
+ </>
+ }
>
- <ItemButton className="it__user-update" onClick={() => setOpenForm('update')}>
- {isInstanceManaged ? translate('update_scm') : translate('update_details')}
- </ItemButton>
- {!isInstanceManaged && user.local && (
- <ItemButton className="it__user-change-password" onClick={() => setOpenForm('password')}>
- {translate('my_profile.password.title')}
- </ItemButton>
- )}
- {isUserActive(user) && !isInstanceManaged && <ItemDivider />}
- {isUserActive(user) && (!isInstanceManaged || isUserLocal) && (
- <ItemDangerButton
- className="it__user-deactivate"
- onClick={() => setOpenForm('deactivate')}
- >
- {translate('users.deactivate')}
- </ItemDangerButton>
- )}
- </ActionsDropdown>
+ <ButtonIcon
+ id={`user-settings-action-dropdown-${user.login}`}
+ className="it__user-actions-toggle"
+ Icon={IconMoreVertical}
+ ariaLabel={translateWithParameters('users.manage_user', user.login)}
+ variety={ButtonVariety.DefaultGhost}
+ />
+ </DropdownMenu.Root>
+
{openForm === 'deactivate' && isUserActive(user) && (
<DeactivateForm onClose={() => setOpenForm(undefined)} user={user} />
)}
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 9d8855acb15..51b8b12c5bb 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,8 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { IconMoreVertical, Spinner, Tooltip } from '@sonarsource/echoes-react';
-import { ActionCell, Avatar, ContentCell, InteractiveIcon, TableRow } from 'design-system';
+import {
+ ButtonIcon,
+ ButtonSize,
+ ButtonVariety,
+ IconMoreVertical,
+ Spinner,
+} from '@sonarsource/echoes-react';
+import { ActionCell, Avatar, ContentCell, TableRow } from 'design-system';
import * as React from 'react';
import DateFromNow from '../../../components/intl/DateFromNow';
import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -80,30 +86,31 @@ export default function UserListItem(props: Readonly<UserListItemProps>) {
<Spinner isLoading={groupsAreLoading}>
{groupsCount}
{manageProvider === undefined && (
- <Tooltip content={translate('users.update_groups')}>
- <InteractiveIcon
- Icon={IconMoreVertical}
- className="it__user-groups sw-ml-2"
- aria-label={translateWithParameters('users.update_users_groups', user.login)}
- onClick={() => setOpenGroupForm(true)}
- size="small"
- />
- </Tooltip>
+ <ButtonIcon
+ Icon={IconMoreVertical}
+ tooltipContent={translate('users.update_groups')}
+ className="it__user-groups sw-ml-2"
+ ariaLabel={translateWithParameters('users.update_users_groups', user.login)}
+ onClick={() => setOpenGroupForm(true)}
+ size={ButtonSize.Medium}
+ variety={ButtonVariety.DefaultGhost}
+ />
)}
</Spinner>
</ContentCell>
<ContentCell>
<Spinner isLoading={tokensAreLoading}>
{tokens?.length}
- <Tooltip content={translateWithParameters('users.update_tokens')}>
- <InteractiveIcon
- Icon={IconMoreVertical}
- className="it__user-tokens sw-ml-2"
- aria-label={translateWithParameters('users.update_tokens_for_x', name ?? login)}
- onClick={() => setOpenTokenForm(true)}
- size="small"
- />
- </Tooltip>
+
+ <ButtonIcon
+ Icon={IconMoreVertical}
+ tooltipContent={translateWithParameters('users.update_tokens')}
+ className="it__user-tokens sw-ml-2"
+ ariaLabel={translateWithParameters('users.update_tokens_for_x', name ?? login)}
+ onClick={() => setOpenTokenForm(true)}
+ size={ButtonSize.Medium}
+ variety={ButtonVariety.DefaultGhost}
+ />
</Spinner>
</ContentCell>
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopupHelper.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
index 96bb70c5c5b..cd4afdd4a4b 100644
--- a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
+++ b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
@@ -19,12 +19,12 @@
*/
import {
+ ButtonIcon,
+ ButtonVariety,
DropdownMenu,
DropdownMenuAlign,
IconQuestionMark,
- Tooltip,
} from '@sonarsource/echoes-react';
-import { InteractiveIcon } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { EmbedDocsPopup } from './EmbedDocsPopup';
@@ -37,18 +37,13 @@ export default function EmbedDocsPopupHelper() {
id="help-menu-dropdown"
items={<EmbedDocsPopup />}
>
- <Tooltip content={translate('help')}>
- <InteractiveIcon
- Icon={IconQuestionMark}
- data-guiding-id="issue-5"
- aria-controls="help-menu-dropdown"
- aria-haspopup
- aria-label={translate('help')}
- currentColor
- size="medium"
- stopPropagation={false}
- />
- </Tooltip>
+ <ButtonIcon
+ Icon={IconQuestionMark}
+ data-guiding-id="issue-5"
+ ariaLabel={translate('help')}
+ isIconFilled
+ variety={ButtonVariety.DefaultGhost}
+ />
</DropdownMenu.Root>
</div>
);