Skip to content

Commit

Permalink
Enable accessible kebab
Browse files Browse the repository at this point in the history
  • Loading branch information
marshmalien committed Jul 11, 2024
1 parent 55628d5 commit 84a127e
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 76 deletions.
185 changes: 111 additions & 74 deletions framework/PageActions/PageActionDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { ButtonVariant, Tooltip } from '@patternfly/react-core';
import {
ButtonVariant,
Divider,
Dropdown,
DropdownItem,
DropdownPosition,
DropdownSeparator,
DropdownToggle,
KebabToggle,
} from '@patternfly/react-core/deprecated';
import { CircleIcon } from '@patternfly/react-icons';
DropdownList,
DropdownPopperProps,
Icon,
MenuToggle,
MenuToggleElement,
Tooltip,
} from '@patternfly/react-core';
import { CircleIcon, EllipsisVIcon } from '@patternfly/react-icons';
import { ComponentClass, FunctionComponent, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { PFColorE, getPatternflyColor } from '../components/pfcolors';
import { getID } from '../hooks/useID';
Expand Down Expand Up @@ -40,7 +42,7 @@ interface PageActionDropdownProps<T extends object> {
isDisabled?: string | undefined;
label?: string;
onOpen?: (label: string, open: boolean) => void;
position?: DropdownPosition;
position?: DropdownPopperProps['position'];
selectedItem?: T;
selectedItems?: T[];
tooltip?: string;
Expand Down Expand Up @@ -97,64 +99,15 @@ export function PageActionDropdown<T extends object>(props: PageActionDropdownPr
const id = getID(props.label ?? 'actions-dropdown');

if (actions.length === 0) return <></>;
const Icon = icon;
const isPrimary =
variant === ButtonVariant.primary || (hasBulkActions && !!selectedItems?.length);
/** Turn primary button to secondary if there are items selected */
const isSecondary =
variant === ButtonVariant.primary && !hasBulkActions && !!selectedItems?.length;
const Toggle =
label || Icon ? (
<DropdownToggle
id="toggle-dropdown"
isDisabled={!!isDisabled}
onToggle={() => setDropdownOpen(!dropdownOpen)}
toggleVariant={isSecondary ? 'secondary' : isPrimary ? 'primary' : undefined}
toggleIndicator={Icon && iconOnly ? null : undefined}
style={isPrimary && !label ? { color: 'var(--pf-v5-global--Color--light-100)' } : {}}
icon={Icon ? <Icon /> : undefined}
data-cy={id}
>
{iconOnly ? undefined : label}
</DropdownToggle>
) : (
<KebabToggle
className="toggle-kebab"
isDisabled={!!isDisabled}
onToggle={() => setDropdownOpen(!dropdownOpen)}
toggleVariant={isPrimary ? 'primary' : undefined}
style={isPrimary && !label ? { color: 'var(--pf-v5-global--Color--light-100)' } : {}}
data-cy={id}
/>
);
const dropdown = (
<Dropdown
onSelect={() => setDropdownOpen(false)}
toggle={Toggle}
isOpen={dropdownOpen}
isPlain={!label || iconOnly}
dropdownItems={actions.map((action, index) => (
<PageDropdownActionItem
key={'label' in action ? action.label : `action-${index}`}
action={action}
selectedItems={selectedItems ?? []}
selectedItem={selectedItem}
hasIcons={hasIcons}
hasSwitches={hasSwitches}
index={index}
data-cy={id}
/>
))}
position={position}
// ZIndex 400 is needed for PF table stick headers
style={{ zIndex: dropdownOpen ? 400 : undefined, padding: 0 }}
className={
props.variant === ButtonVariant.control ? 'pf-v5-c-button pf-m-control' : undefined
}
/>
);
let tooltipContent;
const isKebab = Boolean(!label && !icon);
const CustomIcon = icon;

let tooltipContent;
if (isDisabled) {
tooltipContent = isDisabled;
} else if (tooltip) {
Expand All @@ -165,9 +118,65 @@ export function PageActionDropdown<T extends object>(props: PageActionDropdownPr
tooltipContent = undefined;
}

const dropdownMenuLabel: string | JSX.Element | undefined =
iconOnly && CustomIcon ? (
<Icon>
<CustomIcon />
</Icon>
) : (
label
);

return (
<Tooltip content={tooltipContent} trigger={tooltipContent ? undefined : 'manual'}>
{dropdown}
<Dropdown
isOpen={dropdownOpen}
onSelect={() => setDropdownOpen(false)}
onOpenChange={(isOpen) => setDropdownOpen(isOpen)}
popperProps={{
appendTo: () => document.body,
preventOverflow: true,
enableFlip: true,
position: position,
}}
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
data-cy={id}
id={isKebab ? 'toggle-kebab' : 'toggle-dropdown'}
className={isKebab ? 'toggle-kebab' : 'toggle-dropdown'}
isDisabled={!!isDisabled}
aria-label={isKebab ? 'kebab dropdown toggle' : 'dropdown toggle'}
variant={isSecondary ? 'secondary' : isPrimary ? 'primary' : 'plain'}
onClick={() => setDropdownOpen(!dropdownOpen)}
isExpanded={dropdownOpen}
style={isPrimary && !label ? { color: 'var(--pf-v5-global--Color--light-100)' } : {}}
icon={
CustomIcon ? (
<Icon>
<CustomIcon />
</Icon>
) : undefined
}
>
{dropdownMenuLabel ?? <EllipsisVIcon />}
</MenuToggle>
)}
>
<DropdownList>
{actions.map((action, index) => (
<PageDropdownActionItem
key={'label' in action ? action.label : `action-${index}`}
action={action}
selectedItems={selectedItems ?? []}
selectedItem={selectedItem}
hasIcons={hasIcons}
hasSwitches={hasSwitches}
index={index}
/>
))}
</DropdownList>
</Dropdown>
</Tooltip>
);
}
Expand All @@ -187,8 +196,8 @@ function PageDropdownActionItem<T extends object>(props: {

switch (action.type) {
case PageActionType.Button: {
let Icon: ComponentClass | FunctionComponent | undefined = action.icon;
if (!Icon && hasIcons) Icon = TransparentIcon;
let CustomIcon: ComponentClass | FunctionComponent | undefined = action.icon;
if (!CustomIcon && hasIcons) CustomIcon = TransparentIcon;
let tooltip;

if (isDisabled) {
Expand All @@ -206,11 +215,14 @@ function PageDropdownActionItem<T extends object>(props: {
tooltip = t(`Select at least one item from the list`);
isButtonDisabled = true;
}

return (
<Tooltip key={action.label} content={tooltip} trigger={tooltip ? undefined : 'manual'}>
<StyledDropdownItem $hasSwitches={hasSwitches} $isDanger={Boolean(action.isDanger)}>
<DropdownItem
icon={Icon ? <Icon /> : undefined}
id={getID(action)}
data-cy={getID(action)?.split('.').join('-')}
isAriaDisabled={isButtonDisabled}
onClick={() => {
switch (action.selection) {
case PageActionSelection.None:
Expand All @@ -224,10 +236,22 @@ function PageDropdownActionItem<T extends object>(props: {
break;
}
}}
isAriaDisabled={isButtonDisabled}
id={getID(action)}
data-cy={getID(action)?.split('.').join('-')}
style={{
color:
action.isDanger && !isDisabled ? getPatternflyColor(PFColorE.Danger) : undefined,
}}
>
{CustomIcon ? (
<Icon
size="lg"
iconSize="md"
style={{
paddingRight: '.5rem',
}}
>
<CustomIcon />
</Icon>
) : undefined}
{action.label}
</DropdownItem>
</StyledDropdownItem>
Expand All @@ -236,8 +260,8 @@ function PageDropdownActionItem<T extends object>(props: {
}

case PageActionType.Link: {
let Icon: ComponentClass | FunctionComponent | undefined = action.icon;
if (!Icon && hasIcons) Icon = TransparentIcon;
let CustomIcon: ComponentClass | FunctionComponent | undefined = action.icon;
if (!CustomIcon && hasIcons) CustomIcon = TransparentIcon;
const tooltip = isDisabled ? isDisabled : action.tooltip;
let to: string;

Expand All @@ -258,14 +282,27 @@ function PageDropdownActionItem<T extends object>(props: {
return (
<Tooltip key={action.label} content={tooltip} trigger={tooltip ? undefined : 'manual'}>
<DropdownItem
to={to}
isAriaDisabled={Boolean(isDisabled)}
icon={Icon ? <Icon /> : undefined}
component={<Link to={to}>{action.label}</Link>}
data-cy={getID(action)?.split('.').join('-')}
style={{
color:
action.isDanger && !isDisabled ? getPatternflyColor(PFColorE.Danger) : undefined,
}}
/>
>
{CustomIcon ? (
<Icon
size="lg"
iconSize="md"
style={{
paddingRight: '.5rem',
}}
>
<CustomIcon />
</Icon>
) : undefined}
{action.label}
</DropdownItem>
</Tooltip>
);
}
Expand Down Expand Up @@ -295,7 +332,7 @@ function PageDropdownActionItem<T extends object>(props: {
}

case PageActionType.Seperator:
return <DropdownSeparator key={`separator-${index}`} />;
return <Divider component="li" key={`separator-${index}`} />;
}
}

Expand Down
2 changes: 1 addition & 1 deletion framework/PageInputs/PageMultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ export function PageMultiSelect<
isOpen={open}
onOpenChange={setOpen}
toggle={Toggle}
popperProps={{ appendTo: () => document.body }}
popperProps={{ appendTo: () => document.body, preventOverflow: true, enableFlip: true }}
innerRef={selectListRef}
>
<MenuSearch>
Expand Down
2 changes: 1 addition & 1 deletion framework/PageInputs/PageSingleSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ export function PageSingleSelect<
isOpen={open}
onOpenChange={setOpen}
toggle={props.toggle ?? Toggle}
popperProps={{ appendTo: () => document.body }}
popperProps={{ appendTo: () => document.body, preventOverflow: true, enableFlip: true }}
shouldFocusToggleOnSelect
innerRef={selectListRef}
>
Expand Down
1 change: 1 addition & 0 deletions framework/PageToolbar/PageToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export function PageToolbar<T extends object>(props: PageToolbarProps<T>) {
<PageToolbarToggleGroupContext.Provider value={{ activeGroup, setActiveGroup }}>
<Toolbar
ouiaId="page-toolbar"
data-cy="page-toolbar"
clearAllFilters={clearAllFilters}
className="page-table-toolbar border-bottom"
style={{
Expand Down

0 comments on commit 84a127e

Please sign in to comment.