- (
label={itemToString(item)}
role="menuitemradio"
aria-checked={item === selection}
- renderIcon={item === selection ? Checkmark : undefined}
onClick={(e) => {
handleClick(item, e);
}}
diff --git a/packages/react/src/components/MenuButton/MenuButton-test.js b/packages/react/src/components/MenuButton/MenuButton-test.js
index e3b7b7ddf78e..bbe18e47b4bc 100644
--- a/packages/react/src/components/MenuButton/MenuButton-test.js
+++ b/packages/react/src/components/MenuButton/MenuButton-test.js
@@ -9,7 +9,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
-import { MenuItem, MenuItemSelectable, MenuItemRadioGroup } from '../Menu';
+import { MenuItem } from '../Menu';
import { MenuButton } from './';
@@ -115,57 +115,8 @@ describe('MenuButton', () => {
expect(screen.getByRole('menu')).toBeInTheDocument();
expect(screen.getByRole('menuitem')).toHaveTextContent(/^Action$/);
});
-
- it('warns when MenuItemSelectable is used in children', async () => {
- const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
-
- render(
-
-
-
- );
-
- await userEvent.click(screen.getByRole('button'));
-
- expect(spy).toHaveBeenCalled();
- spy.mockRestore();
- });
-
- it('warns when MenuItemRadioGoup is used in children', async () => {
- const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
-
- render(
-
-
-
- );
-
- await userEvent.click(screen.getByRole('button'));
-
- expect(spy).toHaveBeenCalled();
- spy.mockRestore();
- });
-
- it('warns when a nested Menu is used in children', async () => {
- const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
-
- render(
-
-
-
- );
-
- await userEvent.click(screen.getByRole('button'));
-
- expect(spy).toHaveBeenCalled();
- spy.mockRestore();
- });
});
+
describe('supports props.menuAlignment', () => {
const alignments = [
'top',
diff --git a/packages/react/src/components/MenuButton/MenuButton.stories.js b/packages/react/src/components/MenuButton/MenuButton.stories.js
index 18ffd5b36900..9b59604a0723 100644
--- a/packages/react/src/components/MenuButton/MenuButton.stories.js
+++ b/packages/react/src/components/MenuButton/MenuButton.stories.js
@@ -121,6 +121,22 @@ export const WithIcons = (args) => (
WithIcons.argTypes = { ...sharedArgTypes };
+export const WithNestedMenu = (args) => (
+
+
+
+
+
+
+
+
+
+
+
+);
+
+WithNestedMenu.argTypes = { ...sharedArgTypes };
+
export const WithMenuAlignment = () => (
<>
diff --git a/packages/react/src/components/MenuButton/index.tsx b/packages/react/src/components/MenuButton/index.tsx
index 2ab8748a0786..8fc880fdca62 100644
--- a/packages/react/src/components/MenuButton/index.tsx
+++ b/packages/react/src/components/MenuButton/index.tsx
@@ -130,7 +130,6 @@ const MenuButton = forwardRef(
})
);
}
-
const { refs, floatingStyles, placement, middlewareData } = useFloating({
placement: menuAlignment,
@@ -140,6 +139,16 @@ const MenuButton = forwardRef(
// https://floating-ui.com/docs/misc#clipping
strategy: 'fixed',
+ // Submenus are using a fixed position to break out of the parent menu's
+ // box avoiding clipping while allowing for vertical scroll. When an
+ // element is using transform it establishes a new containing block
+ // block for all of its descendants. Therefore, its padding box will be
+ // used for fixed-positioned descendants. This would cause the submenu
+ // to be clipped by its parent menu.
+ // Reference: https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#current-transformation-matrix-computation
+ // Reference: https://github.com/carbon-design-system/carbon/pull/18153#issuecomment-2498548835
+ transform: false,
+
// Middleware order matters, arrow should be last
middleware: middlewares,
whileElementsMounted: autoUpdate,
@@ -155,7 +164,16 @@ const MenuButton = forwardRef(
useLayoutEffect(() => {
Object.keys(floatingStyles).forEach((style) => {
if (refs.floating.current) {
- refs.floating.current.style[style] = floatingStyles[style];
+ let value = floatingStyles[style];
+
+ if (
+ ['top', 'right', 'bottom', 'left'].includes(style) &&
+ Number(value)
+ ) {
+ value += 'px';
+ }
+
+ refs.floating.current.style[style] = value;
}
});
}, [floatingStyles, refs.floating, middlewareData, placement, open]);
@@ -206,7 +224,6 @@ const MenuButton = forwardRef(
id={id}
legacyAutoalign={false}
label={label}
- mode="basic"
size={size}
open={open}
onClose={handleClose}
diff --git a/packages/styles/scss/components/menu-button/_menu-button.scss b/packages/styles/scss/components/menu-button/_menu-button.scss
index acced1261605..0ef33b85d648 100644
--- a/packages/styles/scss/components/menu-button/_menu-button.scss
+++ b/packages/styles/scss/components/menu-button/_menu-button.scss
@@ -30,19 +30,4 @@
.#{$prefix}--menu-button__trigger--open svg {
transform: rotate(180deg);
}
-
- // Menu alignment classes
- $popover-offset: custom-property.get-var('popover-offset', 3rem);
-
- .#{$prefix}--menu-button__top {
- transform: translate(0, calc(-100% - $popover-offset));
- }
-
- .#{$prefix}--menu-button__top-start {
- transform: translate(0, calc(-100% - $popover-offset));
- }
-
- .#{$prefix}--menu-button__top-end {
- transform: translate(0, calc(-100% - $popover-offset));
- }
}
diff --git a/packages/styles/scss/components/menu/_menu.scss b/packages/styles/scss/components/menu/_menu.scss
index d294a13358b5..17d3a79a9e21 100644
--- a/packages/styles/scss/components/menu/_menu.scss
+++ b/packages/styles/scss/components/menu/_menu.scss
@@ -97,14 +97,6 @@
}
}
- .#{$prefix}--menu-item__icon {
- display: none;
- }
-
- .#{$prefix}--menu--with-icons .#{$prefix}--menu-item__icon {
- display: flex;
- }
-
.#{$prefix}--menu-item__label {
overflow: hidden;
text-overflow: ellipsis;
@@ -120,18 +112,74 @@
@include component-reset.reset;
}
+ .#{$prefix}--menu-item__icon,
+ .#{$prefix}--menu-item__selection-icon {
+ display: none;
+ }
+
.#{$prefix}--menu--with-icons > .#{$prefix}--menu-item,
.#{$prefix}--menu--with-icons
> .#{$prefix}--menu-item-group
> ul
> .#{$prefix}--menu-item,
.#{$prefix}--menu--with-icons
+ > .#{$prefix}--menu-item-radio-group
+ > ul
+ > .#{$prefix}--menu-item,
+ .#{$prefix}--menu--with-selectable-items > .#{$prefix}--menu-item,
+ .#{$prefix}--menu--with-selectable-items
+ > .#{$prefix}--menu-item-group
+ > ul
+ > .#{$prefix}--menu-item,
+ .#{$prefix}--menu--with-selectable-items
> .#{$prefix}--menu-item-radio-group
> ul
> .#{$prefix}--menu-item {
grid-template-columns: 1rem 1fr max-content;
}
+ .#{$prefix}--menu--with-icons
+ > .#{$prefix}--menu-item
+ > .#{$prefix}--menu-item__icon,
+ .#{$prefix}--menu--with-icons
+ > .#{$prefix}--menu-item-group
+ > ul
+ > .#{$prefix}--menu-item
+ > .#{$prefix}--menu-item__icon,
+ .#{$prefix}--menu--with-icons
+ > .#{$prefix}--menu-item-radio-group
+ > ul
+ > .#{$prefix}--menu-item
+ > .#{$prefix}--menu-item__icon,
+ .#{$prefix}--menu--with-selectable-items
+ > .#{$prefix}--menu-item
+ > .#{$prefix}--menu-item__selection-icon,
+ .#{$prefix}--menu--with-selectable-items
+ > .#{$prefix}--menu-item-group
+ > ul
+ > .#{$prefix}--menu-item
+ > .#{$prefix}--menu-item__selection-icon,
+ .#{$prefix}--menu--with-selectable-items
+ > .#{$prefix}--menu-item-radio-group
+ > ul
+ > .#{$prefix}--menu-item
+ > .#{$prefix}--menu-item__selection-icon {
+ display: flex;
+ }
+
+ .#{$prefix}--menu--with-icons.#{$prefix}--menu--with-selectable-items
+ > .#{$prefix}--menu-item,
+ .#{$prefix}--menu--with-icons.#{$prefix}--menu--with-selectable-items
+ > .#{$prefix}--menu-item-group
+ > ul
+ > .#{$prefix}--menu-item,
+ .#{$prefix}--menu--with-icons.#{$prefix}--menu--with-selectable-items
+ > .#{$prefix}--menu-item-radio-group
+ > ul
+ > .#{$prefix}--menu-item {
+ grid-template-columns: 1rem 1rem 1fr max-content;
+ }
+
.#{$prefix}--menu-item--disabled {
color: $text-disabled;
cursor: not-allowed;