Skip to content

fix(ResponsiveActions): Disable kebab when all actions are disabled#928

Open
rhamilto wants to merge 3 commits intopatternfly:mainfrom
rhamilto:fix/responsive-actions-kebab-disabled-927
Open

fix(ResponsiveActions): Disable kebab when all actions are disabled#928
rhamilto wants to merge 3 commits intopatternfly:mainfrom
rhamilto:fix/responsive-actions-kebab-disabled-927

Conversation

@rhamilto
Copy link
Copy Markdown
Member

@rhamilto rhamilto commented May 6, 2026

Description

Fixes #927

This PR implements the PatternFly design guideline that a kebab menu should be disabled when all of its actions are disabled.

Changes

Implementation

  • Created component that uses to access state
  • Kebab disabled state is now responsive to viewport width:
    • Above breakpoint: Disabled only if all regular (non-pinned) items are disabled (pinned items show as separate buttons)
    • Below breakpoint: Disabled if all items (both pinned and regular) are disabled (pinned items move into the kebab)
  • Tracks disabled state separately for pinned vs regular items

Testing

  • Added 5 comprehensive test cases covering all scenarios
  • All 113 tests passing
  • Snapshots updated

Behavior

Before

  • Kebab was always enabled, even when all actions were disabled
  • Poor UX: users could click the kebab to see a list of disabled actions

After

  • Kebab is automatically disabled when all applicable actions are disabled
  • Disabled state responds to browser resizing (pinned items moving in/out of kebab)
  • Follows PatternFly accessibility guidelines

Backward Compatibility

Fully backward compatible

  • No changes to public API or component props
  • No breaking changes
  • Progressive enhancement only

Screenshots/Examples

The kebab will now properly show as disabled when appropriate, improving accessibility and UX.

Checklist

  • Kebab is disabled when all dropdown actions are disabled
  • Kebab disabled state responds correctly to browser resizing
  • Above breakpoint: only regular items affect kebab disabled state
  • Below breakpoint: both pinned and regular items affect kebab disabled state
  • Fully backward compatible (no breaking changes to public API)
  • Comprehensive test coverage for all scenarios
  • All tests passing
  • Snapshots updated

Fixes patternfly#927

- Uses OverflowMenuContext to access isBelowBreakpoint state
- Kebab disabled state is now responsive to viewport width:
  - Above breakpoint: disabled if all regular items are disabled
  - Below breakpoint: disabled if all items (pinned + regular) are disabled
- Created ResponsiveActionsDropdown component to access context
- Tracks disabled state separately for pinned vs regular items
- Added comprehensive test coverage for all scenarios
- Fully backward compatible (no breaking changes)
@patternfly-build
Copy link
Copy Markdown

patternfly-build commented May 6, 2026

@rhamilto
Copy link
Copy Markdown
Member Author

rhamilto commented May 7, 2026

@nicolethoen, one more fix for openshift/console#16203.

This fix isn't required for openshift/console#16203 since it is not adding actions inside <ResponsiveActions> (I had to add a separate "slot" in the toolbar outside <ResonsiveActions> in order to get the desired dropdown functionality), but it will be needed long term.

Copy link
Copy Markdown
Contributor

@thatblindgeye thatblindgeye left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nitpicks below, not blocking to me to get this in, though

}

export const ResponsiveActions: FunctionComponent<ResponsiveActionsProps> = ({ ouiaId = 'ResponsiveActions', breakpoint = 'lg', children, ...props }: ResponsiveActionsProps) => {
// Inner component that has access to OverflowMenuContext
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicks: can we remove a bunch of the comments added in this PR? They aren't totally necessary since the surrounding code will explain what things are doing in this case.

const [ isOpen, setIsOpen ] = useState(false);
const { isBelowBreakpoint } = useContext(OverflowMenuContext);

// Determine if kebab should be disabled based on breakpoint
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment

Comment on lines +34 to +41
// Below breakpoint: pinned items are IN the dropdown, so check all dropdown items
// Disabled only if both pinned AND regular items exist and are all disabled
return (pinnedItemsDisabled.length > 0 || regularItemsDisabled.length > 0) &&
(pinnedItemsDisabled.length === 0 || allPinnedDisabled) &&
(regularItemsDisabled.length === 0 || allRegularDisabled);
} else {
// Above breakpoint: pinned items are shown as buttons, only check regular items
// Disabled only if there are regular items and they're all disabled
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment regarding the comments here

{children}
</OverflowMenuDropdownItem>
);
// Track disabled state separately for pinned vs regular items
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment

Address PR review feedback by removing unnecessary comments that don't add value beyond what the code already expresses.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@rhamilto
Copy link
Copy Markdown
Member Author

rhamilto commented May 8, 2026

Some nitpicks below, not blocking to me to get this in, though

Thanks, @thatblindgeye. Claude loves comments. I asked it to remove them. It did not object. :)

Comment on lines +61 to +70
const { container } = render(
<ResponsiveActions breakpoint="lg">
<ResponsiveAction isDisabled>Disabled action 1</ResponsiveAction>
<ResponsiveAction isDisabled>Disabled action 2</ResponsiveAction>
</ResponsiveActions>);

// Kebab toggle should be disabled when all dropdown items are disabled
const kebabToggle = container.querySelector('[data-ouia-component-id="ResponsiveActions-menu-dropdown-toggle"]');
expect(kebabToggle).toHaveAttribute('disabled');
expect(container).toMatchSnapshot();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the container.querySelectors be changed to screen.getByRole('button')s to better align with RTL conventions?

Also I think we could probably remove the snapshot tests from these unless there's something about the structure explicitly that we're trying to check, and the associated container destructuring.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

import { Button, Dropdown, DropdownList, MenuToggle, OverflowMenu, OverflowMenuContent, OverflowMenuControl, OverflowMenuDropdownItem, OverflowMenuGroup, OverflowMenuItem, OverflowMenuProps } from '@patternfly/react-core';
import { EllipsisVIcon } from '@patternfly/react-icons';
import { ResponsiveActionProps } from '../ResponsiveAction';
import { OverflowMenuContext } from '@patternfly/react-core/dist/esm/components/OverflowMenu/OverflowMenuContext';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, with this not being something we explicitly export from react-core or document as part of our public API I'm not sure about importing it here. I would definitely be against it in a product, but maybe it's ok to do stuff like that in an extension? WDYT @kmcfaul @thatblindgeye ?

Copy link
Copy Markdown
Member Author

@rhamilto rhamilto May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will argue it or something like it should be exported. It reduces the need for duplication. For example, I am using it in OpenShift console because to my knowledge, PatternFly doesn't offer a comparable solution where a menu toggle changes based on viewport. Perhaps this is just a gap in PatternFly. But by exposing this, it frees up developers to solve for problems PatternFly doesn't cover using PatternFly tools.

…napshots

Address PR review feedback:
- Replace container.querySelector with screen.getByRole('button') queries
- Remove snapshot tests from disabled state tests (structure is tested by other tests)
- Remove container destructuring where no longer needed
- Use toBeDisabled()/toBeEnabled() instead of toHaveAttribute('disabled')

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ResponsiveActions: Kebab menu should be disabled when all actions are disabled

5 participants