From e30e7642f726fd4ec4686a4ae12c5b3d62e292fd Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 9 May 2023 13:38:45 -0400 Subject: [PATCH 1/3] fix(Tabs): fixed keyboard use and positioning of TabsAndTable dropdowns --- .../src/components/Dropdown/DropdownItem.tsx | 7 ++-- .../src/demos/examples/DashboardWrapper.js | 2 +- .../src/demos/examples/Tabs/TabsAndTable.tsx | 40 +++++++++++++++++-- .../src/components/Table/ActionsColumn.tsx | 18 ++++++--- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/packages/react-core/src/components/Dropdown/DropdownItem.tsx b/packages/react-core/src/components/Dropdown/DropdownItem.tsx index f2c97e6b245..0f5dab0254a 100644 --- a/packages/react-core/src/components/Dropdown/DropdownItem.tsx +++ b/packages/react-core/src/components/Dropdown/DropdownItem.tsx @@ -27,7 +27,7 @@ export interface DropdownItemProps extends Omit, OUIAProps ouiaSafe?: boolean; } -const DropdownItemBase: React.FunctionComponent = ({ +const DropdownItemBase: React.FunctionComponent = ({ children, className, description, @@ -42,12 +42,12 @@ const DropdownItemBase: React.FunctionComponent = ({ const ouiaProps = useOUIAProps(DropdownItem.displayName, ouiaId, ouiaSafe); return ( @@ -55,7 +55,8 @@ const DropdownItemBase: React.FunctionComponent = ({ ); }; -export const DropdownItem = React.forwardRef((props: DropdownItemProps, ref: React.Ref) => ( + +export const DropdownItem = React.forwardRef((props: DropdownItemProps, ref: React.Ref) => ( )); diff --git a/packages/react-core/src/demos/examples/DashboardWrapper.js b/packages/react-core/src/demos/examples/DashboardWrapper.js index 186f3363f6b..1f48967ffa2 100644 --- a/packages/react-core/src/demos/examples/DashboardWrapper.js +++ b/packages/react-core/src/demos/examples/DashboardWrapper.js @@ -58,7 +58,7 @@ export default class DashboardWrapper extends React.Component { header, sidebar, sidebarNavOpen, - onPageResize, + onPageResize = () => {}, hasNoBreadcrumb, notificationDrawer, isNotificationDrawerExpanded, diff --git a/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx b/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx index 4dbd35c2617..14b877738d0 100644 --- a/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx +++ b/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx @@ -56,6 +56,7 @@ import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-i import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon'; import FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon'; import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon'; +import { KeyTypes } from '../../../helpers'; interface Repository { name: string; @@ -135,15 +136,45 @@ export const TablesAndTabs = () => { } ]; + const firstActionRef = React.useRef(null); + + const handleClick = (event: React.MouseEvent, ActionsToggleProps: CustomActionsToggleProps) => { + const { onToggle } = ActionsToggleProps; + + onToggle(event); + event.stopPropagation(); + }; + + const handleKeyDown = (event: React.KeyboardEvent, ActionsToggleProps: CustomActionsToggleProps) => { + const { onToggle } = ActionsToggleProps; + const { Enter, Space, Escape, ArrowDown, ArrowUp } = KeyTypes; + + const shouldToggle = [Enter, Space, Escape].includes(event.key); + const shouldFocus = [ArrowDown, ArrowUp, Enter, Space].includes(event.key); + + if (shouldToggle) { + event.preventDefault(); + event.stopPropagation(); + onToggle(event); + } + + if (shouldFocus) { + setTimeout(() => { + firstActionRef.current?.focus(); + }, 0); + } + }; + const customActionsToggle = (props: CustomActionsToggleProps, toggleName: string) => ( { - props.onToggle(event); - event.stopPropagation(); - }} + onClick={(event: React.MouseEvent) => handleClick(event, props)} + onKeyDown={(event: React.KeyboardEvent) => handleKeyDown(event, props)} variant="plain" aria-label={`${toggleName} actions`} + aria-haspopup="menu" + isExpanded={props.isOpen} + ref={props.toggleRef} > @@ -270,6 +301,7 @@ export const TablesAndTabs = () => { customActionsToggle(props, repo.name)} + firstActionItemRef={firstActionRef} /> diff --git a/packages/react-table/src/components/Table/ActionsColumn.tsx b/packages/react-table/src/components/Table/ActionsColumn.tsx index acfa1f64c86..c2004b7f2e9 100644 --- a/packages/react-table/src/components/Table/ActionsColumn.tsx +++ b/packages/react-table/src/components/Table/ActionsColumn.tsx @@ -8,7 +8,7 @@ import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-ico import { Tooltip } from '@patternfly/react-core/dist/esm/components/Tooltip'; export interface CustomActionsToggleProps { - onToggle: (event: React.MouseEvent) => void; + onToggle: (event: React.MouseEvent | React.KeyboardEvent) => void; isOpen: boolean; isDisabled: boolean; toggleRef: React.Ref; @@ -28,6 +28,8 @@ export interface ActionsColumnProps extends Omit, ' popperProps?: any; /** @hide Forwarded ref */ innerRef?: React.Ref; + /** Ref to forward to the first item in the popup menu */ + firstActionItemRef?: React.Ref; } const ActionsColumnBase: React.FunctionComponent = ({ @@ -40,6 +42,8 @@ const ActionsColumnBase: React.FunctionComponent = ({ position: 'right', direction: 'down' }, + innerRef, + firstActionItemRef, ...props }: ActionsColumnProps) => { const [isOpen, setIsOpen] = React.useState(false); @@ -103,15 +107,16 @@ const ActionsColumnBase: React.FunctionComponent = ({ ) } {...(rowData && rowData.actionProps)} + ref={innerRef} {...props} popperProps={popperProps} > {items .filter((item) => !item.isOutsideDropdown) - .map(({ title, itemKey, onClick, tooltip, tooltipProps, isSeparator, ...props }, key) => { + .map(({ title, itemKey, onClick, tooltip, tooltipProps, isSeparator, ...props }, index) => { if (isSeparator) { - return ; + return ; } const item = ( = ({ onToggle(); }} {...props} - key={itemKey || key} - data-key={itemKey || key} + key={itemKey || index} + data-key={itemKey || index} + ref={index === 0 ? firstActionItemRef : undefined} > {title} @@ -129,7 +135,7 @@ const ActionsColumnBase: React.FunctionComponent = ({ if (tooltip) { return ( - + {item} ); From f5450135dc4093fbb247ddf6a8fa41873ce2cec5 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Thu, 11 May 2023 13:47:21 -0400 Subject: [PATCH 2/3] chore(Tabs): added comments to explain need for action toggle handlers --- .../src/demos/examples/Tabs/TabsAndTable.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx b/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx index 14b877738d0..956ac4c99f6 100644 --- a/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx +++ b/packages/react-core/src/demos/examples/Tabs/TabsAndTable.tsx @@ -138,14 +138,16 @@ export const TablesAndTabs = () => { const firstActionRef = React.useRef(null); - const handleClick = (event: React.MouseEvent, ActionsToggleProps: CustomActionsToggleProps) => { + /** Handles when the user clicks on the custom action toggle, stops propagation to prevent the drawer from opening */ + const handleActionsToggleClick = (event: React.MouseEvent, ActionsToggleProps: CustomActionsToggleProps) => { const { onToggle } = ActionsToggleProps; onToggle(event); event.stopPropagation(); }; - const handleKeyDown = (event: React.KeyboardEvent, ActionsToggleProps: CustomActionsToggleProps) => { + /** Enables keyboard navigation of the custom actions toggle */ + const handleActionsToggleKeyDown = (event: React.KeyboardEvent, ActionsToggleProps: CustomActionsToggleProps) => { const { onToggle } = ActionsToggleProps; const { Enter, Space, Escape, ArrowDown, ArrowUp } = KeyTypes; @@ -168,8 +170,8 @@ export const TablesAndTabs = () => { const customActionsToggle = (props: CustomActionsToggleProps, toggleName: string) => ( handleClick(event, props)} - onKeyDown={(event: React.KeyboardEvent) => handleKeyDown(event, props)} + onClick={(event: React.MouseEvent) => handleActionsToggleClick(event, props)} + onKeyDown={(event: React.KeyboardEvent) => handleActionsToggleKeyDown(event, props)} variant="plain" aria-label={`${toggleName} actions`} aria-haspopup="menu" From 74dc87c16fbf501b02a36b4b8956239bb89a211c Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Mon, 15 May 2023 10:42:51 -0400 Subject: [PATCH 3/3] chore(DropdownItem): expanded type for ref --- packages/react-core/src/components/Dropdown/DropdownItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-core/src/components/Dropdown/DropdownItem.tsx b/packages/react-core/src/components/Dropdown/DropdownItem.tsx index 0f5dab0254a..c0bffe924d9 100644 --- a/packages/react-core/src/components/Dropdown/DropdownItem.tsx +++ b/packages/react-core/src/components/Dropdown/DropdownItem.tsx @@ -56,7 +56,7 @@ const DropdownItemBase: React.FunctionComponent = ({ ); }; -export const DropdownItem = React.forwardRef((props: DropdownItemProps, ref: React.Ref) => ( +export const DropdownItem = React.forwardRef((props: DropdownItemProps, ref: React.Ref) => ( ));