Skip to content

Commit

Permalink
Menu and Friends workstream (#15398)
Browse files Browse the repository at this point in the history
* feat: added top direction

* feat: adding ellipse check

* feat: adding alignments to menu

* feat: added alignment working

* feat: added ellipsis to combobutton

* fix: added aalignments and ellipsis

* fix: fixed popover

* fix: fixed bugs in alignment

* fix: fixed prop name

* fix: added tests to ComboButton

* feat: added story manipulation

* test: updated snapshots

* fix: changed style file re-use in other components

* fix: fixed menu size when the combobutton is larger

* fix: added comment

* docs: added docs to menubutton

* test: updated snapshots

* fix: changed css and docs

* feat: added menuAlignment to overflowMenu

* fix: slipt css for menuAligment due to different block-sizes

* test: added test for different files

* fix: added new stories for MenuButton

* fix: added box shadow to top alignment

* fix: fixed visual changes in percy

* fix: added the menuAlignment to playground

* fix: fixed typo on the scss

* Update _menu.scss

remove comments

* Update packages/react/src/components/Menu/Menu.tsx

Co-authored-by: TJ Egan <[email protected]>

* Update packages/react/src/components/Menu/Menu.tsx

Co-authored-by: TJ Egan <[email protected]>

* Update index.js

* Update Menu.tsx

* fix: fixed alignment naming

* fix: removed console.log

* fix: experimental property added to menualignment

* test: fixed tests

* fix: fixed ref and menuAlignment names

* docs: fixed docs

* fix: snapshots

* Update packages/react/src/components/Menu/Menu.tsx

* Update packages/react/src/components/ComboButton/ComboButton.mdx

* fix: removed test playground code

---------

Co-authored-by: Andrea N. Cardona <[email protected]>
Co-authored-by: TJ Egan <[email protected]>
Co-authored-by: Taylor Jones <[email protected]>
  • Loading branch information
4 people authored Feb 5, 2024
1 parent b4464c1 commit 722b329
Show file tree
Hide file tree
Showing 18 changed files with 536 additions and 11 deletions.
29 changes: 29 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,19 @@ Map {
"isRequired": true,
"type": "string",
},
"menuAlignment": Object {
"args": Array [
Array [
"top",
"top-start",
"top-end",
"bottom",
"bottom-start",
"bottom-end",
],
],
"type": "oneOf",
},
"onClick": Object {
"type": "func",
},
Expand Down Expand Up @@ -4470,6 +4483,9 @@ Map {
"label": Object {
"type": "string",
},
"menuAlignment": Object {
"type": "string",
},
"mode": Object {
"args": Array [
Array [
Expand Down Expand Up @@ -4568,6 +4584,19 @@ Map {
"isRequired": true,
"type": "string",
},
"menuAlignment": Object {
"args": Array [
Array [
"top",
"top-start",
"top-end",
"bottom",
"bottom-start",
"bottom-end",
],
],
"type": "oneOf",
},
"size": Object {
"args": Array [
Array [
Expand Down
45 changes: 43 additions & 2 deletions packages/react/src/components/ComboButton/ComboButton-test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable testing-library/no-node-access */
/**
* Copyright IBM Corp. 2023
*
Expand Down Expand Up @@ -106,14 +107,40 @@ describe('ComboButton', () => {
</ComboButton>
);

// eslint-disable-next-line testing-library/no-node-access
expect(container.firstChild.lastChild).toHaveClass(
`${prefix}--popover--${alignment}`
);
});
});
});

describe('supports props.menuAlignment', () => {
const alignments = [
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
];

alignments.forEach((alignment) => {
it(`menuAlignment="${alignment}"`, async () => {
render(
<ComboButton label="Primary action" menuAlignment={alignment}>
<MenuItem label="Additional action" />
</ComboButton>
);

await userEvent.click(screen.getAllByRole('button')[1]);

expect(screen.getByRole('menu')).toHaveClass(
`${prefix}--combo-button__${alignment}`
);
});
});
});

it('supports props.translateWithId', () => {
const t = () => 'test';

Expand All @@ -125,7 +152,6 @@ describe('ComboButton', () => {

const triggerButton = screen.getAllByRole('button')[1];
const tooltipId = triggerButton.getAttribute('aria-labelledby');
// eslint-disable-next-line testing-library/no-node-access
const tooltip = document.getElementById(tooltipId);

expect(tooltip).toHaveTextContent(t());
Expand Down Expand Up @@ -210,5 +236,20 @@ describe('ComboButton', () => {
expect(spy).toHaveBeenCalled();
spy.mockRestore();
});

it('supports ellipsis in ComboButton by checking the className', async () => {
render(
<ComboButton label="Primary action super long text to enable ellipsis">
<MenuItem label="Submenu">
<MenuItem label="Action" />
</MenuItem>
</ComboButton>
);

expect(
screen.getByTitle('Primary action super long text to enable ellipsis')
.parentElement
).toHaveClass(`${prefix}--combo-button__primary-action`);
});
});
});
13 changes: 13 additions & 0 deletions packages/react/src/components/ComboButton/ComboButton.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ disclosed list next to the primary action. These additional actions must be
</ComboButton>
```

## Menu Alignment (experimental)

The `menuAlignment` prop enables you to define the placement of the Menu in
relation to the `ComboButton`. For instance, setting `menuAlignment="top"` on
the `ComboButton` will render the Menu above the button.

If it seems your specified `menuAlignment` isn't working, it's because we
prioritize ensuring the Menu remains visible. We calculate the optimal position
to display the Menu in case the provided `menuAlignment` obscures it.

We encourage you to play around in the Storybook playground to better understand
the `menuAlignment` prop.

## Component API

<ArgsTable />
Expand Down
64 changes: 61 additions & 3 deletions packages/react/src/components/ComboButton/ComboButton.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ export default {

export const Default = () => (
<ComboButton label="Primary action">
<MenuItem label="Second action" />
<MenuItem label="Second action with a long label description" />
<MenuItem label="Third action" />
<MenuItem label="Fourth action" disabled />
</ComboButton>
);

export const WithDanger = () => (
<ComboButton label="Primary action">
<MenuItem label="Second action" />
<MenuItem label="Second action with a long label description" />
<MenuItem label="Third action" />
<MenuItem label="Fourth action" />
<MenuItemDivider />
Expand All @@ -53,12 +53,70 @@ export const WithIcons = () => (
</ComboButton>
);

export const WithMenuAlignment = () => (
<>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<ComboButton label="Bottom" menuAlignment="bottom">
<MenuItem label="Second action with a long label description" />
<MenuItem label="Third action" />
<MenuItem label="Fourth action" disabled />
</ComboButton>

<ComboButton label="Bottom start" menuAlignment="bottom-start">
<MenuItem label="Second action with a long label description" />
<MenuItem label="Third action" />
<MenuItem label="Fourth action" disabled />
</ComboButton>

<ComboButton label="Bottom end" menuAlignment="bottom-end">
<MenuItem label="Second action with a long label description" />
<MenuItem label="Third action" />
<MenuItem label="Fourth action" disabled />
</ComboButton>
</div>

<div
style={{
display: 'flex',
marginTop: '15rem',
justifyContent: 'space-between',
}}>
<ComboButton label="Top" menuAlignment="top" tooltipAlignment="bottom">
<MenuItem label="Second action with a long label description" />
<MenuItem label="Third action" />
<MenuItem label="Fourth action" disabled />
</ComboButton>

<ComboButton
label="Top start"
menuAlignment="top-start"
tooltipAlignment="bottom">
<MenuItem label="Second action with a long label description" />
<MenuItem label="Third action" />
<MenuItem label="Fourth action" disabled />
</ComboButton>

<ComboButton
label="Top end"
menuAlignment="top-end"
tooltipAlignment="bottom">
<MenuItem label="Second action with a long label description" />
<MenuItem label="Third action" />
<MenuItem label="Fourth action" disabled />
</ComboButton>
</div>
</>
);

export const Playground = (args) => {
const onClick = action('onClick (MenuItem)');

return (
<ComboButton {...args}>
<MenuItem label="Second action" onClick={onClick} />
<MenuItem
label="Second action with a long label description"
onClick={onClick}
/>
<MenuItem label="Third action" onClick={onClick} />
<MenuItem label="Fourth action" disabled onClick={onClick} />
<MenuItemDivider />
Expand Down
25 changes: 24 additions & 1 deletion packages/react/src/components/ComboButton/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const ComboButton = React.forwardRef(function ComboButton(
label,
onClick,
size = 'lg',
menuAlignment = 'bottom',
tooltipAlignment,
translateWithId: t = defaultTranslateWithId,
...rest
Expand All @@ -44,7 +45,6 @@ const ComboButton = React.forwardRef(function ComboButton(
) {
const id = useId('combobutton');
const prefix = usePrefix();

const containerRef = useRef(null);
const menuRef = useRef(null);
const ref = useMergedRefs([forwardRef, containerRef]);
Expand Down Expand Up @@ -74,6 +74,11 @@ const ComboButton = React.forwardRef(function ComboButton(

function handleOpen() {
menuRef.current.style.inlineSize = `${width}px`;
menuRef.current.style.minInlineSize = `${width}px`;

if (menuAlignment !== 'bottom' && menuAlignment !== 'top') {
menuRef.current.style.inlineSize = `fit-content`;
}
}

const containerClasses = classNames(
Expand All @@ -85,6 +90,8 @@ const ComboButton = React.forwardRef(function ComboButton(
className
);

const menuClasses = classNames(`${prefix}--combo-button__${menuAlignment}`);

const primaryActionClasses = classNames(
`${prefix}--combo-button__primary-action`
);
Expand All @@ -98,6 +105,7 @@ const ComboButton = React.forwardRef(function ComboButton(
aria-owns={open ? id : null}>
<div className={primaryActionClasses}>
<Button
title={label}
size={size}
disabled={disabled}
onClick={handlePrimaryActionClick}>
Expand All @@ -118,6 +126,9 @@ const ComboButton = React.forwardRef(function ComboButton(
<ChevronDown />
</IconButton>
<Menu
containerRef={containerRef}
menuAlignment={menuAlignment}
className={menuClasses}
ref={menuRef}
id={id}
label={t('carbon.combo-button.additional-actions')}
Expand Down Expand Up @@ -155,6 +166,18 @@ ComboButton.propTypes = {
*/
label: PropTypes.string.isRequired,

/**
* Experimental property. Specify how the menu should align with the button element
*/
menuAlignment: PropTypes.oneOf([
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
]),

/**
* Provide an optional function to be called when the primary action element is clicked.
*/
Expand Down
Loading

0 comments on commit 722b329

Please sign in to comment.