diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2024-07-22 11:25:25 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-07-24 20:02:49 +0000 |
commit | 87c9b7fad2fdf0672997c4e27d15dc3606931535 (patch) | |
tree | 280133dc1b355569f9c5f60d850ea121f1df2ee8 /server/sonar-web | |
parent | 04de2de3d71fceab34404f5bb047671658dd1bb9 (diff) | |
download | sonarqube-87c9b7fad2fdf0672997c4e27d15dc3606931535.tar.gz sonarqube-87c9b7fad2fdf0672997c4e27d15dc3606931535.zip |
SONAR-22524 Replace InteractiveIcons with ButtonIcon
Diffstat (limited to 'server/sonar-web')
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> ); |