diff --git a/gradle/changelog/file_action_menu_accessibility.yaml b/gradle/changelog/file_action_menu_accessibility.yaml new file mode 100644 index 0000000000..6a42db667d --- /dev/null +++ b/gradle/changelog/file_action_menu_accessibility.yaml @@ -0,0 +1,2 @@ +- type: fixed + description: Broken file action menu keyboard interaction diff --git a/scm-ui/ui-extensions/src/extensionPoints.ts b/scm-ui/ui-extensions/src/extensionPoints.ts index d9ff84dd21..2d89a575b7 100644 --- a/scm-ui/ui-extensions/src/extensionPoints.ts +++ b/scm-ui/ui-extensions/src/extensionPoints.ts @@ -433,7 +433,7 @@ export type RepositoryAvatar = RenderableExtensionPointDefinition< */ export type PrimaryRepositoryAvatar = RenderableExtensionPointDefinition< "repos.repository-avatar.primary", - { repository: Repository, size: number } + { repository: Repository; size: number } >; /** @@ -644,7 +644,7 @@ type BaseActionBarOverflowMenuProps = { category: string; label: string; icon: string; - props?: unknown; + props?: Record; }; export type ActionMenuProps = BaseActionBarOverflowMenuProps & { action: (props: ContentActionExtensionProps) => void }; @@ -669,15 +669,14 @@ export type RepositoryDeleteButton = RenderableExtensionPointDefinition< export type RepositoryInformationTableBottom = RenderableExtensionPointDefinition< "repository.information.table.bottom", { repository: Repository } - >; +>; export type UserInformationTableBottom = RenderableExtensionPointDefinition< "user.information.table.bottom", { user: User } - >; +>; export type GroupInformationTableBottom = RenderableExtensionPointDefinition< "group.information.table.bottom", { group: Group } - >; - +>; diff --git a/scm-ui/ui-overlays/src/menu/Menu.tsx b/scm-ui/ui-overlays/src/menu/Menu.tsx index 5b3e148ce7..a714c49151 100644 --- a/scm-ui/ui-overlays/src/menu/Menu.tsx +++ b/scm-ui/ui-overlays/src/menu/Menu.tsx @@ -32,6 +32,8 @@ import { Link as ReactRouterLink, LinkProps as ReactRouterLinkProps } from "reac const MenuContent = styled(RadixMenu.Content)` border: var(--scm-border); background-color: var(--scm-secondary-background); + z-index: 400; + position: relative; `; const MenuItem = styled(RadixMenu.Item).attrs({ diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json index 00712368c6..6d38dff24c 100644 --- a/scm-ui/ui-webapp/public/locales/de/repos.json +++ b/scm-ui/ui-webapp/public/locales/de/repos.json @@ -362,6 +362,7 @@ "file": "Datei" }, "content": { + "actionMenuTrigger": "Aktionen", "historyButton": "History", "sourcesButton": "Sources", "annotateButton": "Annotate", diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json index f27feb04d6..cf809768e9 100644 --- a/scm-ui/ui-webapp/public/locales/en/repos.json +++ b/scm-ui/ui-webapp/public/locales/en/repos.json @@ -362,6 +362,7 @@ "file": "File" }, "content": { + "actionMenuTrigger": "Actions", "historyButton": "History", "sourcesButton": "Sources", "annotateButton": "Annotate", diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ActionMenuItem.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ActionMenuItem.tsx index 2078b7da67..09e24456be 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ActionMenuItem.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ActionMenuItem.tsx @@ -24,36 +24,22 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; -import classNames from "classnames"; import { Icon } from "@scm-manager/ui-components"; import { extensionPoints } from "@scm-manager/ui-extensions"; -import { MenuItemContainer } from "./ContentActionMenu"; +import { Menu } from "@scm-manager/ui-overlays"; const ActionMenuItem: FC< extensionPoints.ActionMenuProps & { - active: boolean; - onClick: (event: React.MouseEvent) => void; extensionProps: extensionPoints.ContentActionExtensionProps; } -> = ({ action, active, label, icon, props, extensionProps, ...rest }) => { +> = ({ action, label, props, icon, extensionProps }) => { const [t] = useTranslation("plugins"); return ( - { - rest.onClick(event); - action(extensionProps); - }} - > - - {t(label)} - + action(extensionProps)} {...props}> + + {t(label)} + ); }; diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ContentActionMenu.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ContentActionMenu.tsx index f365224a7d..51ac029d42 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ContentActionMenu.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ContentActionMenu.tsx @@ -24,36 +24,12 @@ import { binder, extensionPoints } from "@scm-manager/ui-extensions"; import React, { FC, ReactElement, useState } from "react"; -import { Icon } from "@scm-manager/ui-components"; import styled from "styled-components"; -import { Menu } from "@headlessui/react"; +import { Menu } from "@scm-manager/ui-overlays"; import FallbackMenuButton from "./FallbackMenuButton"; import MenuItem from "./MenuItem"; - -const MenuButton = styled(Menu.Button)` - background: transparent; - border: none; - font-size: 1.5rem; - height: 2.5rem; - width: 50px; - margin-bottom: 0.5rem; -`; - -const MenuItems = styled(Menu.Items)` - padding: 0.5rem; - position: absolute; - z-index: 999; - width: max-content; - border: var(--scm-border); - border-radius: 5px; - background-color: var(--scm-secondary-background); - box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02); -`; - -export const MenuItemContainer = styled.div` - border-radius: 5px; - padding: 0.5rem; -`; +import { Icon } from "@scm-manager/ui-buttons"; +import { useTranslation } from "react-i18next"; const HR = styled.hr` margin: 0.25rem; @@ -65,6 +41,7 @@ type Props = { }; const ContentActionMenu: FC = ({ extensionProps }) => { + const [t] = useTranslation("repos"); const [selectedModal, setSelectedModal] = useState(); const extensions = binder.getExtensions( "repos.sources.content.actionbar.menu", @@ -81,47 +58,6 @@ const ContentActionMenu: FC = ({ extensionProps }) => { {} ); - const renderMenu = () => ( - <> - - {({ open }) => ( - <> - - - - {open && ( -
- - {Object.entries(categories).map(([_category, extensionSet], index) => ( - <> - {extensionSet.map((extension) => ( - - {({ active }) => { - return ( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore onClick prop required but gets provided implicit by the Menu.Item from headless ui - - ); - }} - - ))} - {Object.keys(categories).length > index + 1 ?
: null} - - ))} -
-
- )} - - )} -
- - ); - if (extensions.length <= 0) { return null; } @@ -135,7 +71,30 @@ const ContentActionMenu: FC = ({ extensionProps }) => { setSelectedModal={setSelectedModal} /> ) : ( - renderMenu() + + ellipsis-v + + } + > + {Object.entries(categories).map(([_category, extensionSet], index) => ( + <> + {extensionSet.map((extension) => ( + + ))} + {Object.keys(categories).length > index + 1 ?
: null} + + ))} +
)} {selectedModal || null} diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/LinkMenuItem.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/LinkMenuItem.tsx index c0381f2a08..19a4bbd467 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/LinkMenuItem.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/LinkMenuItem.tsx @@ -24,11 +24,11 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; -import classNames from "classnames"; import { Icon } from "@scm-manager/ui-components"; import styled from "styled-components"; import { Link } from "react-router-dom"; import { extensionPoints } from "@scm-manager/ui-extensions"; +import { Menu } from "@scm-manager/ui-overlays"; const MenuItemLinkContainer = styled(Link)<{ active: boolean }>` border-radius: 5px; @@ -40,24 +40,15 @@ const MenuItemLinkContainer = styled(Link)<{ active: boolean }>` `; const LinkMenuItem: FC< - extensionPoints.LinkMenuProps & { active: boolean; extensionProps: extensionPoints.ContentActionExtensionProps } -> = ({ link, active, label, icon, props, extensionProps, ...rest }) => { + extensionPoints.LinkMenuProps & { extensionProps: extensionPoints.ContentActionExtensionProps } +> = ({ link, label, props, icon, extensionProps }) => { const [t] = useTranslation("plugins"); return ( - - + + {t(label)} - + ); }; diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/MenuItem.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/MenuItem.tsx index 77e4eb2b65..2fb3895ff8 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/MenuItem.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/MenuItem.tsx @@ -30,12 +30,10 @@ import { extensionPoints } from "@scm-manager/ui-extensions"; const MenuItem: FC< extensionPoints.FileViewActionBarOverflowMenu["type"] & { - active: boolean; - onClick: (event: React.MouseEvent) => void; setSelectedModal: (element: ReactElement | undefined) => void; extensionProps: extensionPoints.ContentActionExtensionProps; } -> = ({ extensionProps, label, icon, props, category, active, onClick, setSelectedModal, ...rest }) => { +> = ({ extensionProps, label, icon, props, category, setSelectedModal, ...rest }) => { if ("action" in rest) { return ( @@ -56,7 +52,6 @@ const MenuItem: FC< category={category} label={label} icon={icon} - active={active} extensionProps={extensionProps} props={props} {...rest} @@ -70,8 +65,6 @@ const MenuItem: FC< label={label} icon={icon} extensionProps={extensionProps} - active={active} - onClick={onClick} setSelectedModal={setSelectedModal} props={props} {...rest} diff --git a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ModalMenuItem.tsx b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ModalMenuItem.tsx index 6c4df6b689..67e36fe37b 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ModalMenuItem.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/content/overflowMenu/ModalMenuItem.tsx @@ -24,39 +24,30 @@ import React, { FC, ReactElement } from "react"; import { useTranslation } from "react-i18next"; -import classNames from "classnames"; import { Icon } from "@scm-manager/ui-components"; -import { MenuItemContainer } from "./ContentActionMenu"; import { extensionPoints } from "@scm-manager/ui-extensions"; +import { Menu } from "@scm-manager/ui-overlays"; const ModalMenuItem: FC< extensionPoints.ModalMenuProps & { - active: boolean; - onClick: (event: React.MouseEvent) => void; setSelectedModal: (element: ReactElement | undefined) => void; extensionProps: extensionPoints.ContentActionExtensionProps; } -> = ({ modalElement, active, label, icon, props, extensionProps, setSelectedModal, ...rest }) => { +> = ({ modalElement, label, icon, props, extensionProps, setSelectedModal }) => { const [t] = useTranslation("plugins"); return ( - { + setSelectedModal( React.createElement(modalElement, { ...extensionProps, close: () => setSelectedModal(undefined) }) - ); - rest.onClick(event); - }} + ) + } + {...props} > - + {t(label)} - + ); };