From: David Cho-Lerat Date: Thu, 11 Jul 2024 12:50:44 +0000 (+0200) Subject: SONAR-22453 Use DropdownMenu from Echoes X-Git-Tag: 10.7.0.96327~363 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=f158ff6131dac0c332039c300ad2257d317d25c0;p=sonarqube.git SONAR-22453 Use DropdownMenu from Echoes --- diff --git a/server/sonar-web/design-system/src/components/Dropdown.tsx b/server/sonar-web/design-system/src/components/Dropdown.tsx index d79f7d011bc..ab273c08f0c 100644 --- a/server/sonar-web/design-system/src/components/Dropdown.tsx +++ b/server/sonar-web/design-system/src/components/Dropdown.tsx @@ -61,6 +61,9 @@ interface State { open: boolean; } +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export class Dropdown extends React.PureComponent, State> { state: State = { open: false }; @@ -142,6 +145,9 @@ interface ActionsDropdownProps extends Omit { toggleClassName?: string; } +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export function ActionsDropdown(props: Readonly) { const { children, buttonSize, ariaLabel, toggleClassName, ...dropdownProps } = props; diff --git a/server/sonar-web/design-system/src/components/DropdownMenu.tsx b/server/sonar-web/design-system/src/components/DropdownMenu.tsx index 335f28f434e..c8027c1fc24 100644 --- a/server/sonar-web/design-system/src/components/DropdownMenu.tsx +++ b/server/sonar-web/design-system/src/components/DropdownMenu.tsx @@ -39,6 +39,9 @@ interface Props extends React.HtmlHTMLAttributes { size?: InputSizeKeys; } +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export function DropdownMenu({ children, className, @@ -75,6 +78,9 @@ type ItemLinkProps = Omit & innerRef?: React.Ref; }; +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export function ItemLink(props: ItemLinkProps) { const { children, @@ -111,6 +117,9 @@ interface ItemNavLinkProps extends ItemLinkProps { end?: boolean; } +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export function ItemNavLink(props: ItemNavLinkProps) { const { children, className, disabled, end, icon, onClick, selected, innerRef, to, ...liProps } = props; @@ -138,6 +147,9 @@ interface ItemButtonProps extends ListItemProps { onClick: React.MouseEventHandler; } +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export const ItemButton = forwardRef( (props: ItemButtonProps, ref: ForwardedRef) => { const { children, className, disabled, icon, innerRef, onClick, selected, ...liProps } = props; @@ -258,6 +270,9 @@ export const ItemHeaderHighlight = styled.span` font-weight: 600; `; +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export const ItemHeader = styled.li` background-color: ${themeColor('dropdownMenuHeader')}; color: ${themeContrast('dropdownMenuHeader')}; @@ -266,6 +281,9 @@ export const ItemHeader = styled.li` `; ItemHeader.defaultProps = { className: 'dropdown-menu-header', role: 'menuitem' }; +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export const ItemDivider = styled.li` height: 1px; background-color: ${themeColor('popupBorder')}; diff --git a/server/sonar-web/design-system/src/components/DropdownToggler.tsx b/server/sonar-web/design-system/src/components/DropdownToggler.tsx index 10d959b7128..c41ea91ab8b 100644 --- a/server/sonar-web/design-system/src/components/DropdownToggler.tsx +++ b/server/sonar-web/design-system/src/components/DropdownToggler.tsx @@ -31,6 +31,9 @@ interface Props extends PopupProps { withFocusOutHandler?: boolean; } +/** @deprecated Use DropdownMenu.Root and other DropdownMenu.* elements from Echoes instead. + * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3354918914/DropdownMenus | Migration Guide} + */ export function DropdownToggler(props: Props) { const { children, diff --git a/server/sonar-web/design-system/src/components/MainMenuItem.tsx b/server/sonar-web/design-system/src/components/MainMenuItem.tsx index cef7db8ed9e..c2f33f554bf 100644 --- a/server/sonar-web/design-system/src/components/MainMenuItem.tsx +++ b/server/sonar-web/design-system/src/components/MainMenuItem.tsx @@ -49,10 +49,14 @@ export const MainMenuItem = styled.li` } &:hover, - &.hover, - &[aria-expanded='true'] { + &.hover { border-bottom: ${themeBorder('active', 'menuBorder', 1)}; color: ${themeContrast('mainBarHover')}; } } + + &[aria-expanded='true'] a { + border-bottom: ${themeBorder('active', 'menuBorder', 1)}; + color: ${themeContrast('mainBarHover')}; + } `; diff --git a/server/sonar-web/design-system/src/components/NavBarTabs.tsx b/server/sonar-web/design-system/src/components/NavBarTabs.tsx index 590cb913605..7d2aecf4447 100644 --- a/server/sonar-web/design-system/src/components/NavBarTabs.tsx +++ b/server/sonar-web/design-system/src/components/NavBarTabs.tsx @@ -17,9 +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 styled from '@emotion/styled'; import classNames from 'classnames'; -import React from 'react'; +import React, { forwardRef } from 'react'; import tw, { theme } from 'twin.macro'; import { themeBorder, themeColor, themeContrast } from '../helpers/theme'; import { isDefined } from '../helpers/types'; @@ -48,29 +49,36 @@ interface NavBarTabLinkProps extends Omit { withChevron?: boolean; } -export function NavBarTabLink(props: NavBarTabLinkProps) { - const { active, children, className, text, withChevron = false, ...linkProps } = props; - return ( - - - classNames( - 'sw-flex sw-items-center', - { active: isDefined(active) ? active : isActive }, - className, - ) - } - {...linkProps} - > - - {text} - - {children} - {withChevron && } - - - ); -} +export const NavBarTabLink = forwardRef( + (props: NavBarTabLinkProps, ref) => { + const { active, children, className, text, withChevron = false, ...linkProps } = props; + return ( + + + classNames( + 'sw-flex sw-items-center', + { active: isDefined(active) ? active : isActive }, + className, + ) + } + ref={ref} + {...linkProps} + > + + {text} + + + {children} + + {withChevron && } + + + ); + }, +); + +NavBarTabLink.displayName = 'NavBarTabLink'; export function DisabledTabLink(props: { label: string; overlay: React.ReactNode }) { return ( @@ -102,11 +110,13 @@ const NavBarTabLinkWrapper = styled.li` & > a.active, & > a:active, & > a:hover, - & > a:focus { + & > a:focus, + & > a[aria-expanded='true'] { border-bottom-color: ${themeColor('tabBorder')}; } & > a.active > span[data-text], + & > a[aria-expanded='true'] > span[data-text], & > a:active > span { ${tw`sw-body-md-highlight`}; } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx index bc8bd1e1bde..55cc488429d 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx @@ -17,14 +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 { - DisabledTabLink, - Dropdown, - ItemNavLink, - NavBarTabLink, - NavBarTabs, - PopupZLevel, -} from 'design-system'; + +import { DropdownMenu } from '@sonarsource/echoes-react'; +import { DisabledTabLink, NavBarTabLink, NavBarTabs } from 'design-system'; import * as React from 'react'; import { useLocation } from '~sonar-aligned/components/hoc/withRouter'; import { getBranchLikeQuery, isPullRequest } from '~sonar-aligned/helpers/branch-like'; @@ -245,33 +240,28 @@ export function Menu(props: Readonly) { isApplication(qualifier), isPortfolioLike(qualifier), ); + if (!adminLinks.some((link) => link != null)) { return null; } return ( - - {({ onToggleClick, open, a11yAttrs }) => ( - - )} - + + ); }; @@ -325,12 +315,12 @@ export function Menu(props: Readonly) { return null; } return ( - {translate('project_settings.page')} - + ); }; @@ -340,12 +330,12 @@ export function Menu(props: Readonly) { } return ( - {translate('project_branch_pull_request.page')} - + ); }; @@ -354,12 +344,12 @@ export function Menu(props: Readonly) { return null; } return ( - {translate('project_baseline.page')} - + ); }; @@ -368,7 +358,7 @@ export function Menu(props: Readonly) { return null; } return ( - ) { }} > {translate('project_dump.page')} - + ); }; @@ -385,7 +375,7 @@ export function Menu(props: Readonly) { return null; } return ( - ) { }} > {translate('project_quality_profiles.page')} - + ); }; @@ -402,12 +392,12 @@ export function Menu(props: Readonly) { return null; } return ( - {translate('project_quality_gate.page')} - + ); }; @@ -416,12 +406,12 @@ export function Menu(props: Readonly) { return null; } return ( - {translate('project_links.page')} - + ); }; @@ -430,12 +420,12 @@ export function Menu(props: Readonly) { return null; } return ( - {translate('permissions.page')} - + ); }; @@ -444,7 +434,7 @@ export function Menu(props: Readonly) { return null; } return ( - ) { }} > {translate('background_tasks.page')} - + ); }; @@ -461,12 +451,12 @@ export function Menu(props: Readonly) { return null; } return ( - {translate('update_key.page')} - + ); }; @@ -475,12 +465,12 @@ export function Menu(props: Readonly) { return null; } return ( - {translate('webhooks.page')} - + ); }; @@ -500,12 +490,12 @@ export function Menu(props: Readonly) { } return ( - {translate('deletion.page')} - + ); }; @@ -513,9 +503,12 @@ export function Menu(props: Readonly) { const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`; const query = { ...baseQuery, qualifier }; return ( - + {name} - + ); }; @@ -538,25 +531,13 @@ export function Menu(props: Readonly) { } return ( - renderExtension(e, false, query))} + items={withoutSecurityExtension.map((e) => renderExtension(e, false, query))} > - {({ onToggleClick, open, a11yAttrs }) => ( - - )} - + + ); }; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Menu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Menu-test.tsx index 2d954bb0bf9..a8b1519b245 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Menu-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Menu-test.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 { screen } from '@testing-library/react'; + +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { ComponentQualifier } from '~sonar-aligned/types/component'; @@ -62,7 +63,7 @@ it('should render correctly', async () => { expect(screen.getByRole('link', { name: 'layout.security_reports' })).toBeInTheDocument(); // Check the dropdown. - const button = screen.getByRole('button', { name: 'more' }); + const button = screen.getByRole('link', { name: 'more' }); expect(button).toBeInTheDocument(); await user.click(button); expect(screen.getByRole('menuitem', { name: 'ComponentFoo' })).toBeInTheDocument(); @@ -101,7 +102,7 @@ it('should render correctly when on a branch', async () => { extensions: [{ key: 'component-foo', name: 'ComponentFoo' }], }, }, - 'branch=normal-branch', + 'id=foo&branch=normal-branch', ); expect(await screen.findByRole('link', { name: 'overview.page' })).toBeInTheDocument(); @@ -119,16 +120,19 @@ it('should render correctly when on a pull request', async () => { extensions: [{ key: 'component-foo', name: 'ComponentFoo' }], }, }, - 'pullRequest=01', + 'id=foo&pullRequest=01', ); expect(await screen.findByRole('link', { name: 'overview.page' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'issues.page' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'layout.measures' })).toBeInTheDocument(); - expect( - screen.queryByRole('link', { name: `layout.settings.${ComponentQualifier.Project}` }), - ).not.toBeInTheDocument(); + await waitFor(() => { + expect( + screen.queryByRole('link', { name: `layout.settings.${ComponentQualifier.Project}` }), + ).not.toBeInTheDocument(); + }); + expect(screen.queryByRole('button', { name: 'project.info.title' })).not.toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx index eb91e17a2e8..84de3342ce8 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx @@ -17,7 +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 { Dropdown, ItemNavLink, MainMenuItem, PopupPlacement, PopupZLevel } from 'design-system'; + +import { DropdownMenu } from '@sonarsource/echoes-react'; +import { MainMenuItem } from 'design-system'; import * as React from 'react'; import { translate } from '../../../../helpers/l10n'; import { AppState } from '../../../../types/appstate'; @@ -26,13 +28,13 @@ import withAppStateContext from '../../app-state/withAppStateContext'; const renderGlobalPageLink = ({ key, name }: Extension) => { return ( - + {name} - + ); }; -function GlobalNavMore({ appState: { globalPages = [] } }: { appState: AppState }) { +function GlobalNavMore({ appState: { globalPages = [] } }: Readonly<{ appState: AppState }>) { const withoutPortfolios = globalPages.filter((page) => page.key !== 'governance/portfolios'); if (withoutPortfolios.length === 0) { @@ -40,29 +42,17 @@ function GlobalNavMore({ appState: { globalPages = [] } }: { appState: AppState } return ( - {withoutPortfolios.map(renderGlobalPageLink)}} - placement={PopupPlacement.BottomLeft} - zLevel={PopupZLevel.Global} + items={withoutPortfolios.map(renderGlobalPageLink)} > - {({ onToggleClick, open }) => ( - - )} - + + + {translate('more')} + + + ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx index 6b63fecf74f..e562c6016c7 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.tsx @@ -17,15 +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 { - Dropdown, - ItemNavLink, - LightLabel, - NavBarTabLink, - NavBarTabs, - PopupZLevel, - TopBar, -} from 'design-system'; + +import { DropdownMenu } from '@sonarsource/echoes-react'; +import { LightLabel, NavBarTabLink, NavBarTabs, TopBar } from 'design-system'; import * as React from 'react'; import { Location } from 'react-router-dom'; import withLocation from '../../../../components/hoc/withLocation'; @@ -88,9 +82,9 @@ export class SettingsNav extends React.PureComponent { renderExtension = ({ key, name }: Extension) => { return ( - + {name} - + ); }; @@ -98,134 +92,125 @@ export class SettingsNav extends React.PureComponent { const extensionsWithoutSupport = this.props.extensions.filter( (extension) => extension.key !== 'license/support', ); + return ( - - + {translate('settings.page')} - + - + {translate('property.category.security.encryption')} - + - + {translate('webhooks.page')} - + {extensionsWithoutSupport.map(this.renderExtension)} } - size="auto" - zLevel={PopupZLevel.Global} > - {({ onToggleClick, open }) => ( - - )} - + + ); } renderProjectsTab() { return ( - - + {translate('management')} - + - + {translate('background_tasks.page')} - + } - size="auto" - zLevel={PopupZLevel.Global} > - {({ onToggleClick, open }) => ( - - )} - + + ); } renderSecurityTab() { return ( - - {translate('users.page')} + + {translate('users.page')} + - {translate('user_groups.page')} + + {translate('user_groups.page')} + - + {translate('global_permissions.page')} - + - + {translate('permission_templates')} - + } - size="auto" - zLevel={PopupZLevel.Global} > - {({ onToggleClick, open }) => ( - - )} - + + ); } render() { const { extensions, pendingPlugins } = this.props; const hasSupportExtension = extensions.find((extension) => extension.key === 'license/support'); + const hasGovernanceExtension = extensions.find( (e) => e.key === AdminPageExtension.GovernanceConsole, ); + const totalPendingPlugins = pendingPlugins.installing.length + pendingPlugins.removing.length + pendingPlugins.updating.length; + let notifComponent; + if (this.props.systemStatus === 'RESTARTING') { notifComponent = ; } else if (totalPendingPlugins > 0) { @@ -266,6 +251,7 @@ export class SettingsNav extends React.PureComponent { )} + {notifComponent} ); diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx index 63852374650..bfa6befdcdd 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx @@ -18,16 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { - ButtonSecondary, - DropdownMenu, - DropdownToggler, - ItemButton, - PopupPlacement, - PopupZLevel, - addGlobalErrorMessage, - addGlobalSuccessMessage, -} from 'design-system'; +import { DropdownMenu } from '@sonarsource/echoes-react'; +import { addGlobalErrorMessage, addGlobalSuccessMessage, ButtonSecondary } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import DocumentationLink from '../../../components/common/DocumentationLink'; @@ -49,11 +41,6 @@ export interface Props { pullRequestID?: string; } -interface State { - disabled?: boolean; - ides: Ide[]; -} - const showError = () => addGlobalErrorMessage( ) { - const [state, setState] = React.useState({ disabled: false, ides: [] }); + const [isDisabled, setIsDisabled] = React.useState(false); + const [ides, setIdes] = React.useState(undefined); + const ref = React.useRef(null); - const cleanState = () => { - setState({ ...state, ides: [] }); - }; + // to give focus back to the trigger button once it is re-rendered as a single button + const focusTriggerButton = React.useCallback(() => { + setTimeout(() => { + ref.current?.focus(); + }); + }, []); const openIssue = async (ide: Ide) => { - setState({ ...state, disabled: true, ides: [] }); // close the dropdown, disable the button + setIsDisabled(true); let token: { name?: string; token?: string } = {}; @@ -112,14 +104,15 @@ export function IssueOpenInIdeButton({ setTimeout( () => { - setState({ ...state, disabled: false }); + setIsDisabled(false); + focusTriggerButton(); }, ide.needsToken ? DELAY_AFTER_TOKEN_CREATION : 0, ); }; - const onClick = async () => { - setState({ ...state, ides: [] }); + const findIDEs = async () => { + setIdes(undefined); const ides = (await probeSonarLintServers()) ?? []; @@ -128,47 +121,51 @@ export function IssueOpenInIdeButton({ } else if (ides.length === 1) { openIssue(ides[0]); } else { - setState({ ...state, ides }); + setIdes(ides); } }; - return ( -
- 1} - overlay={ - - {state.ides.map((ide) => { - const { ideName, description } = ide; - - const label = ideName + (description ? ` - ${description}` : ''); - - return ( - { - openIssue(ide); - }} - > - {label} - - ); - })} - - } - placement={PopupPlacement.BottomLeft} - zLevel={PopupZLevel.Global} - > - - {translate('open_in_ide')} - - -
+ const onClick = ides === undefined ? findIDEs : undefined; + + const triggerButton = ( + + {translate('open_in_ide')} + + ); + + return ides === undefined ? ( + triggerButton + ) : ( + { + const { ideName, description } = ide; + + const label = ideName + (description ? ` - ${description}` : ''); + + return ( + { + openIssue(ide); + }} + > + {label} + + ); + })} + onClose={() => { + setIdes(undefined); + focusTriggerButton(); + }} + onOpen={findIDEs} + > + {triggerButton} + ); } diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 2cfa5efd2c7..7a7383db6b9 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -58,7 +58,7 @@ import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; import { fillBranchLike, isSameBranchLike } from '../../../helpers/branch-like'; import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication'; import { parseIssueFromResponse } from '../../../helpers/issues'; -import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers'; +import { isDropdown, isInput, isShortcut } from '../../../helpers/keyboardEventHelpers'; import { KeyboardKeys } from '../../../helpers/keycodes'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { serializeDate } from '../../../helpers/query'; @@ -280,7 +280,7 @@ export class App extends React.PureComponent { return; } - if (isInput(event) || isShortcut(event)) { + if (isInput(event) || isShortcut(event) || isDropdown(event)) { return; } diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx index 2997e847dd8..008c47f2cd3 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx @@ -17,15 +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 { - ButtonSecondary, - ChevronDownIcon, - Dropdown, - ItemButton, - PopupPlacement, - PopupZLevel, - TextMuted, -} from 'design-system'; + +import { DropdownMenu } from '@sonarsource/echoes-react'; +import { ButtonSecondary, ChevronDownIcon, TextMuted } from 'design-system'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { GraphType } from '../../types/project-activity'; @@ -73,9 +67,9 @@ export default function GraphsHeader(props: Props) { const label = translate('project_activity.graphs', type); return ( - handleGraphChange(type)}> + handleGraphChange(type)}> {label} - + ); }); }, [noCustomGraph, handleGraphChange]); @@ -83,13 +77,7 @@ export default function GraphsHeader(props: Props) { return (
- + - + {isCustomGraph(graph) && props.onAddCustomMetric !== undefined && diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/DocItemLink.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/DocItemLink.tsx index 0ca48ab3ac7..7e795522211 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/DocItemLink.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/DocItemLink.tsx @@ -18,24 +18,18 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { ItemLink, OpenNewTabIcon } from 'design-system'; +import { DropdownMenu } from '@sonarsource/echoes-react'; import * as React from 'react'; import { DocLink } from '../../helpers/doc-links'; import { useDocUrl } from '../../helpers/docs'; interface Props { children: React.ReactNode; - innerRef?: React.Ref; to: DocLink; } -export function DocItemLink({ to, innerRef, children }: Props) { +export function DocItemLink({ to, children }: Readonly) { const toStatic = useDocUrl(to); - return ( - - - {children} - - ); + return {children}; } diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx index 15276793770..2707b60a7c1 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { ItemDivider, ItemHeader, ItemLink, OpenNewTabIcon } from 'design-system'; +import { DropdownMenu } from '@sonarsource/echoes-react'; import * as React from 'react'; import { DocLink } from '../../helpers/doc-links'; import { translate } from '../../helpers/l10n'; @@ -37,40 +37,36 @@ function IconLink({ text: string; }) { return ( - - {text} + + } + to={link} + > {text} - + ); } -function Suggestions({ - firstItemRef, - suggestions, -}: { - firstItemRef: React.RefObject; - suggestions: SuggestionLink[]; -}) { +function Suggestions({ suggestions }: Readonly<{ suggestions: SuggestionLink[] }>) { return ( <> - {translate('docs.suggestion')} - {suggestions.map((suggestion, i) => ( - + {translate('docs.suggestion')} + + {suggestions.map((suggestion) => ( + {suggestion.text} ))} - + + ); } @@ -85,29 +81,38 @@ export function EmbedDocsPopup() { return ( <> - {suggestions.length !== 0 && ( - - )} - - {translate('docs.documentation')} - - {translate('api_documentation.page')} - {translate('api_documentation.page.v2')} - - - + {suggestions.length !== 0 && } + + {translate('docs.documentation')} + + + {translate('api_documentation.page')} + + + + {translate('api_documentation.page.v2')} + + + + + {translate('docs.get_help')} - - - {translate('docs.stay_connected')} + + + + + {translate('docs.stay_connected')} + + + - } - allowResizing - zLevel={PopupZLevel.Global} - > - {({ onToggleClick, open }) => ( - - - - )} - + }> + + + +
); } diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx index ef09bf7b4b0..95b64a1af0e 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx @@ -35,8 +35,6 @@ it('should render with no suggestions', async () => { expect(screen.getByText('docs.documentation')).toBeInTheDocument(); expect(screen.queryByText('docs.suggestion')).not.toBeInTheDocument(); - - expect(screen.getByText('docs.documentation')).toHaveFocus(); }); it('should be able to render with suggestions and remove them', async () => { @@ -51,13 +49,9 @@ it('should be able to render with suggestions and remove them', async () => { expect(screen.getByText('docs.suggestion')).toBeInTheDocument(); expect(screen.getByText('About Background Tasks')).toBeInTheDocument(); - expect(screen.getByText('About Background Tasks')).toHaveFocus(); - await user.click(screen.getByRole('button', { name: 'remove.suggestion' })); await user.click(screen.getByRole('button', { name: 'help' })); expect(screen.queryByText('docs.suggestion')).not.toBeInTheDocument(); - - expect(screen.getByText('docs.documentation')).toHaveFocus(); }); function renderEmbedDocsPopup() { diff --git a/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts b/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts index 65ec4ba0fee..6a73fad10a0 100644 --- a/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts +++ b/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts @@ -36,3 +36,9 @@ export function isInput( event.target instanceof HTMLTextAreaElement ); } + +export function isDropdown(event: KeyboardEvent) { + const role = (event.target as HTMLElement | null)?.role ?? ''; + + return ['menu', 'menuitem'].includes(role); +}