diff --git a/.yarn/cache/@carbon-icon-helpers-npm-10.45.1-3f2ee2290e-69da5ae85b.zip b/.yarn/cache/@carbon-icon-helpers-npm-10.45.1-3f2ee2290e-69da5ae85b.zip deleted file mode 100644 index 1496960232d2..000000000000 Binary files a/.yarn/cache/@carbon-icon-helpers-npm-10.45.1-3f2ee2290e-69da5ae85b.zip and /dev/null differ diff --git a/examples/class-prefix/package.json b/examples/class-prefix/package.json index c2151f5c20cd..9bb77bfd28e1 100644 --- a/examples/class-prefix/package.json +++ b/examples/class-prefix/package.json @@ -1,7 +1,7 @@ { "name": "class-prefix", "private": true, - "version": "0.46.0-rc.0", + "version": "0.46.0", "type": "module", "scripts": { "dev": "vite", @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "@carbon/react": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/codesandbox-styles/package.json b/examples/codesandbox-styles/package.json index 521152daa63e..ef61803a5731 100644 --- a/examples/codesandbox-styles/package.json +++ b/examples/codesandbox-styles/package.json @@ -1,7 +1,7 @@ { "name": "codesandbox-styles", "private": true, - "version": "0.52.0-rc.0", + "version": "0.52.0", "type": "module", "scripts": { "dev": "vite" @@ -11,6 +11,6 @@ "vite": "^4.3.8" }, "dependencies": { - "@carbon/styles": "^1.49.0-rc.0" + "@carbon/styles": "^1.49.0" } } diff --git a/examples/custom-theme/package.json b/examples/custom-theme/package.json index e16ea27a9e29..8153561c5c89 100644 --- a/examples/custom-theme/package.json +++ b/examples/custom-theme/package.json @@ -1,7 +1,7 @@ { "name": "custom-theme", "private": true, - "version": "0.47.0-rc.0", + "version": "0.47.0", "type": "module", "scripts": { "dev": "vite", @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "@carbon/react": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/id-prefix/package.json b/examples/id-prefix/package.json index 614d23eeef1b..ffb0cd8002b0 100644 --- a/examples/id-prefix/package.json +++ b/examples/id-prefix/package.json @@ -1,7 +1,7 @@ { "name": "id-prefix", "private": true, - "version": "0.46.0-rc.0", + "version": "0.46.0", "type": "module", "scripts": { "dev": "vite", @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "@carbon/react": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/incremental-migration-vite/package.json b/examples/incremental-migration-vite/package.json index 428c17bc4a9c..b2324ca4ba72 100644 --- a/examples/incremental-migration-vite/package.json +++ b/examples/incremental-migration-vite/package.json @@ -1,7 +1,7 @@ { "name": "incremental-migration-vite", "private": true, - "version": "0.14.0-rc.0", + "version": "0.14.0", "scripts": { "dev": "vite", "build": "vite build", @@ -12,7 +12,7 @@ }, "dependencies": { "@carbon/icons-react": "^10.49.0", - "@carbon/react": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", "carbon-components": "^10.57.0", "carbon-components-react": "^7.57.0", "carbon-icons": "^7.0.7", diff --git a/examples/light-dark-mode/package.json b/examples/light-dark-mode/package.json index 5dfcd6944ed0..97b25103891d 100644 --- a/examples/light-dark-mode/package.json +++ b/examples/light-dark-mode/package.json @@ -1,7 +1,7 @@ { "name": "examples-light-dark", "private": true, - "version": "0.47.0-rc.0", + "version": "0.47.0", "scripts": { "build": "next build", "dev": "next dev", @@ -9,7 +9,7 @@ "start": "next start" }, "dependencies": { - "@carbon/react": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", "next": "13.5.1", "react": "18.2.0", "react-dom": "18.2.0" diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 439148290d19..f429edb9ccf8 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -1,7 +1,7 @@ { "name": "examples-nextjs", "private": true, - "version": "0.49.0-rc.0", + "version": "0.49.0", "scripts": { "build": "next build", "dev": "next dev", @@ -9,7 +9,7 @@ "start": "next start" }, "dependencies": { - "@carbon/react": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", "next": "13.5.6", "react": "18.2.0", "react-dom": "18.2.0" diff --git a/examples/v10-token-compat-in-v11/package.json b/examples/v10-token-compat-in-v11/package.json index 57faf1be973c..c454e2b8a01e 100644 --- a/examples/v10-token-compat-in-v11/package.json +++ b/examples/v10-token-compat-in-v11/package.json @@ -1,7 +1,7 @@ { "name": "v10-token-compat-in-v11", "private": true, - "version": "0.47.0-rc.0", + "version": "0.47.0", "type": "module", "scripts": { "dev": "vite", @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "@carbon/react": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/vite/package.json b/examples/vite/package.json index d3f7f7729821..eb42b904efd2 100644 --- a/examples/vite/package.json +++ b/examples/vite/package.json @@ -1,7 +1,7 @@ { "name": "vite", "private": true, - "version": "0.47.0-rc.0", + "version": "0.47.0", "type": "module", "scripts": { "dev": "vite", @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "@carbon/react": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/packages/carbon-components-react/package.json b/packages/carbon-components-react/package.json index 3ba0743b819d..9d4017ccf280 100644 --- a/packages/carbon-components-react/package.json +++ b/packages/carbon-components-react/package.json @@ -1,7 +1,7 @@ { "name": "carbon-components-react", "description": "The Carbon Design System is IBM’s open-source design system for products and experiences.", - "version": "8.49.0-rc.0", + "version": "8.49.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -41,8 +41,8 @@ "sass": "^1.33.0" }, "dependencies": { - "@carbon/react": "^1.49.0-rc.0", - "@carbon/styles": "^1.49.0-rc.0", + "@carbon/react": "^1.49.0", + "@carbon/styles": "^1.49.0", "@carbon/telemetry": "0.1.0", "chalk": "1.1.3" }, diff --git a/packages/carbon-components/package.json b/packages/carbon-components/package.json index e46cd34bdc9e..c461688a8faa 100644 --- a/packages/carbon-components/package.json +++ b/packages/carbon-components/package.json @@ -1,7 +1,7 @@ { "name": "carbon-components", "description": "The Carbon Design System is IBM’s open-source design system for products and experiences.", - "version": "11.49.0-rc.0", + "version": "11.49.0", "license": "Apache-2.0", "repository": { "type": "git", @@ -42,7 +42,7 @@ "sass": "^1.33.0" }, "dependencies": { - "@carbon/styles": "^1.49.0-rc.0", + "@carbon/styles": "^1.49.0", "@carbon/telemetry": "0.1.0", "chalk": "1.1.3" }, diff --git a/packages/elements/package.json b/packages/elements/package.json index 3e7c061260a5..600da6b51266 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/elements", "description": "A collection of design elements in code for the IBM Design Language", - "version": "11.38.0-rc.0", + "version": "11.38.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -38,10 +38,10 @@ "dependencies": { "@carbon/colors": "^11.20.0", "@carbon/grid": "^11.21.0", - "@carbon/icons": "^11.35.0-rc.0", + "@carbon/icons": "^11.35.0", "@carbon/layout": "^11.20.0", "@carbon/motion": "^11.16.0", - "@carbon/themes": "^11.30.0-rc.0", + "@carbon/themes": "^11.30.0", "@carbon/type": "^11.25.0" }, "devDependencies": { diff --git a/packages/icon-build-helpers/package.json b/packages/icon-build-helpers/package.json index 82fec725b1ff..9b46a927196c 100644 --- a/packages/icon-build-helpers/package.json +++ b/packages/icon-build-helpers/package.json @@ -2,7 +2,7 @@ "name": "@carbon/icon-build-helpers", "private": true, "description": "Build helpers for the Carbon Design System icon library", - "version": "1.22.0-rc.0", + "version": "1.22.0", "license": "Apache-2.0", "main": "src/index.js", "repository": { @@ -30,7 +30,7 @@ "@babel/template": "^7.16.7", "@babel/types": "^7.18.4", "@carbon/cli-reporter": "^10.7.0", - "@carbon/icon-helpers": "^10.46.0-rc.0", + "@carbon/icon-helpers": "^10.46.0", "@hapi/joi": "^17.1.1", "@rollup/plugin-babel": "^6.0.0", "@rollup/plugin-replace": "^5.0.0", diff --git a/packages/icon-helpers/package.json b/packages/icon-helpers/package.json index de84f1a55c77..cac0b1c02bf5 100644 --- a/packages/icon-helpers/package.json +++ b/packages/icon-helpers/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/icon-helpers", "description": "Helpers used alongside icons for digital and software products using the Carbon Design System", - "version": "10.46.0-rc.0", + "version": "10.46.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", diff --git a/packages/icons-react/package.json b/packages/icons-react/package.json index 69ee998a6043..335a4435cba7 100644 --- a/packages/icons-react/package.json +++ b/packages/icons-react/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/icons-react", "description": "React components for icons in digital and software products using the Carbon Design System", - "version": "11.35.0-rc.0", + "version": "11.35.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -37,13 +37,13 @@ "react": ">=16" }, "dependencies": { - "@carbon/icon-helpers": "^10.46.0-rc.0", + "@carbon/icon-helpers": "^10.46.0", "@carbon/telemetry": "0.1.0", "prop-types": "^15.7.2" }, "devDependencies": { - "@carbon/icon-build-helpers": "^1.22.0-rc.0", - "@carbon/icons": "^11.35.0-rc.0", + "@carbon/icon-build-helpers": "^1.22.0", + "@carbon/icons": "^11.35.0", "rimraf": "^5.0.0" }, "sideEffects": false diff --git a/packages/icons-vue/package.json b/packages/icons-vue/package.json index 9e508e1fb8fd..7edbb673b3d0 100644 --- a/packages/icons-vue/package.json +++ b/packages/icons-vue/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/icons-vue", "description": "Vue components for icons in digital and software products using the Carbon Design System", - "version": "10.84.0-rc.0", + "version": "10.84.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -29,11 +29,11 @@ "clean": "rimraf es lib" }, "dependencies": { - "@carbon/icon-helpers": "^10.46.0-rc.0" + "@carbon/icon-helpers": "^10.46.0" }, "devDependencies": { "@carbon/cli-reporter": "^10.7.0", - "@carbon/icons": "^11.35.0-rc.0", + "@carbon/icons": "^11.35.0", "fs-extra": "^11.0.0", "prettier": "^2.8.8", "rimraf": "^5.0.0", diff --git a/packages/icons/package.json b/packages/icons/package.json index 06be29422319..21ba8061d5af 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/icons", "description": "Icons for digital and software products using the Carbon Design System", - "version": "11.35.0-rc.0", + "version": "11.35.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -40,7 +40,7 @@ }, "devDependencies": { "@carbon/cli": "^11.15.0", - "@carbon/icon-build-helpers": "^1.22.0-rc.0", + "@carbon/icon-build-helpers": "^1.22.0", "rimraf": "^5.0.0" } } diff --git a/packages/pictograms-react/package.json b/packages/pictograms-react/package.json index 0a5d7d146129..085212742e7d 100644 --- a/packages/pictograms-react/package.json +++ b/packages/pictograms-react/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/pictograms-react", "description": "React components for pictograms in digital and software products using the Carbon Design System", - "version": "11.57.0-rc.0", + "version": "11.57.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -38,13 +38,13 @@ "react": ">=16" }, "dependencies": { - "@carbon/icon-helpers": "^10.46.0-rc.0", + "@carbon/icon-helpers": "^10.46.0", "@carbon/telemetry": "0.1.0", "prop-types": "^15.7.2" }, "devDependencies": { - "@carbon/icon-build-helpers": "^1.22.0-rc.0", - "@carbon/pictograms": "^12.31.0-rc.0", + "@carbon/icon-build-helpers": "^1.22.0", + "@carbon/pictograms": "^12.31.0", "rimraf": "^5.0.0" }, "sideEffects": false diff --git a/packages/pictograms/package.json b/packages/pictograms/package.json index 68e68febab47..192d31fcf9e5 100644 --- a/packages/pictograms/package.json +++ b/packages/pictograms/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/pictograms", "description": "Pictograms for digital and software products using the Carbon Design System", - "version": "12.31.0-rc.0", + "version": "12.31.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -32,7 +32,7 @@ "prepublishOnly": "yarn build" }, "devDependencies": { - "@carbon/icon-build-helpers": "^1.22.0-rc.0", + "@carbon/icon-build-helpers": "^1.22.0", "rimraf": "^5.0.0" } } diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 492379b4f38b..b0d781e0f7a0 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -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", }, @@ -4470,6 +4483,9 @@ Map { "label": Object { "type": "string", }, + "menuAlignment": Object { + "type": "string", + }, "mode": Object { "args": Array [ Array [ @@ -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 [ diff --git a/packages/react/package.json b/packages/react/package.json index 1918637c35c0..e76250ba70a7 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "name": "@carbon/react", "description": "React components for the Carbon Design System", - "version": "1.49.0-rc.0", + "version": "1.49.0", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.js", @@ -49,9 +49,9 @@ "dependencies": { "@babel/runtime": "^7.18.3", "@carbon/feature-flags": "^0.16.0", - "@carbon/icons-react": "^11.35.0-rc.0", + "@carbon/icons-react": "^11.35.0", "@carbon/layout": "^11.20.0", - "@carbon/styles": "^1.49.0-rc.0", + "@carbon/styles": "^1.49.0", "@ibm/telemetry-js": "^1.2.0", "classnames": "2.5.1", "copy-to-clipboard": "^3.3.1", @@ -79,7 +79,7 @@ "@babel/preset-react": "^7.22.3", "@babel/preset-typescript": "^7.21.5", "@carbon/test-utils": "^10.30.0", - "@carbon/themes": "^11.30.0-rc.0", + "@carbon/themes": "^11.30.0", "@rollup/plugin-babel": "^6.0.0", "@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-node-resolve": "^15.0.0", diff --git a/packages/react/src/components/ComboButton/ComboButton-test.js b/packages/react/src/components/ComboButton/ComboButton-test.js index 0189db55fc83..653889445daf 100644 --- a/packages/react/src/components/ComboButton/ComboButton-test.js +++ b/packages/react/src/components/ComboButton/ComboButton-test.js @@ -1,3 +1,4 @@ +/* eslint-disable testing-library/no-node-access */ /** * Copyright IBM Corp. 2023 * @@ -106,7 +107,6 @@ describe('ComboButton', () => { ); - // eslint-disable-next-line testing-library/no-node-access expect(container.firstChild.lastChild).toHaveClass( `${prefix}--popover--${alignment}` ); @@ -114,6 +114,33 @@ describe('ComboButton', () => { }); }); + describe('supports props.menuAlignment', () => { + const alignments = [ + 'top', + 'top-start', + 'top-end', + 'bottom', + 'bottom-start', + 'bottom-end', + ]; + + alignments.forEach((alignment) => { + it(`menuAlignment="${alignment}"`, async () => { + render( + + + + ); + + await userEvent.click(screen.getAllByRole('button')[1]); + + expect(screen.getByRole('menu')).toHaveClass( + `${prefix}--combo-button__${alignment}` + ); + }); + }); + }); + it('supports props.translateWithId', () => { const t = () => 'test'; @@ -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()); @@ -210,5 +236,20 @@ describe('ComboButton', () => { expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); + + it('supports ellipsis in ComboButton by checking the className', async () => { + render( + + + + + + ); + + expect( + screen.getByTitle('Primary action super long text to enable ellipsis') + .parentElement + ).toHaveClass(`${prefix}--combo-button__primary-action`); + }); }); }); diff --git a/packages/react/src/components/ComboButton/ComboButton.mdx b/packages/react/src/components/ComboButton/ComboButton.mdx index 8325bbc88959..78083d2425e6 100644 --- a/packages/react/src/components/ComboButton/ComboButton.mdx +++ b/packages/react/src/components/ComboButton/ComboButton.mdx @@ -27,6 +27,19 @@ disclosed list next to the primary action. These additional actions must be ``` +## 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 diff --git a/packages/react/src/components/ComboButton/ComboButton.stories.js b/packages/react/src/components/ComboButton/ComboButton.stories.js index 0f9755520a3e..d4323b6ac49f 100644 --- a/packages/react/src/components/ComboButton/ComboButton.stories.js +++ b/packages/react/src/components/ComboButton/ComboButton.stories.js @@ -30,7 +30,7 @@ export default { export const Default = () => ( - + @@ -38,7 +38,7 @@ export const Default = () => ( export const WithDanger = () => ( - + @@ -53,12 +53,70 @@ export const WithIcons = () => ( ); +export const WithMenuAlignment = () => ( + <> +
+ + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +); + export const Playground = (args) => { const onClick = action('onClick (MenuItem)'); return ( - + diff --git a/packages/react/src/components/ComboButton/index.js b/packages/react/src/components/ComboButton/index.js index 403cb1f159e8..6e719ae4b01b 100644 --- a/packages/react/src/components/ComboButton/index.js +++ b/packages/react/src/components/ComboButton/index.js @@ -36,6 +36,7 @@ const ComboButton = React.forwardRef(function ComboButton( label, onClick, size = 'lg', + menuAlignment = 'bottom', tooltipAlignment, translateWithId: t = defaultTranslateWithId, ...rest @@ -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]); @@ -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( @@ -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` ); @@ -98,6 +105,7 @@ const ComboButton = React.forwardRef(function ComboButton( aria-owns={open ? id : null}>
selectedItems: ItemType[]; } +interface selectedItemType { + text: string; +} + interface MultiSelectSortingProps { /** * Provide a compare function that is used to determine the ordering of @@ -588,14 +592,18 @@ const MultiSelect = React.forwardRef( }); } + const itemsSelectedText = + selectedItems.length > 0 && + selectedItems.map((item) => (item as selectedItemType).text); + return (
diff --git a/packages/react/src/components/OverflowMenu/OverflowMenu.featureflag.stories.js b/packages/react/src/components/OverflowMenu/OverflowMenu.featureflag.stories.js index 716d7d13b75d..a2fa40c5f05a 100644 --- a/packages/react/src/components/OverflowMenu/OverflowMenu.featureflag.stories.js +++ b/packages/react/src/components/OverflowMenu/OverflowMenu.featureflag.stories.js @@ -95,6 +95,63 @@ export const CustomIcon = () => { ); }; +export const WithMenuAlignment = (args) => { + return ( + <> +
+ + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + ); +}; + export const Playground = (args) => { return ( @@ -128,4 +185,11 @@ Playground.argTypes = { disable: true, }, }, + menuAlignment: { + options: ['bottom-start', 'bottom-end', 'top-start', 'top-end'], + control: { type: 'select' }, + description: + 'Specify how the menu should align with the button element `bottom-start` `bottom-end` `top-start` `top-end`', + default: 'bottom-start', + }, }; diff --git a/packages/react/src/components/OverflowMenu/next/OverflowMenu-test.js b/packages/react/src/components/OverflowMenu/next/OverflowMenu-test.js index 90fee14c033c..519c0783a64a 100644 --- a/packages/react/src/components/OverflowMenu/next/OverflowMenu-test.js +++ b/packages/react/src/components/OverflowMenu/next/OverflowMenu-test.js @@ -11,6 +11,8 @@ import { MenuItem } from '../../Menu'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +const prefix = 'cds'; + describe('OverflowMenu (enable-v12-overflowmenu)', () => { it('should render closed by default', () => { render( @@ -103,4 +105,24 @@ describe('OverflowMenu (enable-v12-overflowmenu)', () => { 'false' ); }); + + describe('supports props.menuAlignment', () => { + const alignments = ['top-start', 'top-end', 'bottom-start', 'bottom-end']; + + alignments.forEach((alignment) => { + it(`menuAlignment="${alignment}"`, async () => { + render( + + one + + ); + + await userEvent.click(screen.getByRole('button')); + + expect(screen.getAllByRole('menu')[0]).toHaveClass( + `${prefix}--overflow-menu__${alignment}` + ); + }); + }); + }); }); diff --git a/packages/react/src/components/OverflowMenu/next/OverflowMenu.mdx b/packages/react/src/components/OverflowMenu/next/OverflowMenu.mdx index 2f7d7013462e..ef2984a7399b 100644 --- a/packages/react/src/components/OverflowMenu/next/OverflowMenu.mdx +++ b/packages/react/src/components/OverflowMenu/next/OverflowMenu.mdx @@ -29,6 +29,20 @@ This version of the `OverflowMenu` can be enabled by using the ``` +## Menu Alignment + +The `menuAlignment` prop enables you to define the placement of the Menu in +relation to the `OverflowMenu`. For instance, setting +`menuAlignment="top-start"` on the `OverflowMenu` 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 diff --git a/packages/react/src/components/OverflowMenu/next/index.js b/packages/react/src/components/OverflowMenu/next/index.js index 331be0ad6275..d9abb0707de2 100644 --- a/packages/react/src/components/OverflowMenu/next/index.js +++ b/packages/react/src/components/OverflowMenu/next/index.js @@ -26,6 +26,7 @@ const OverflowMenu = React.forwardRef(function OverflowMenu( label = 'Options', renderIcon: IconElement = OverflowMenuVertical, size = defaultSize, + menuAlignment = 'bottom-start', tooltipAlignment, ...rest }, @@ -35,14 +36,28 @@ const OverflowMenu = React.forwardRef(function OverflowMenu( const prefix = usePrefix(); const triggerRef = useRef(null); - const { open, x, y, handleClick, handleMousedown, handleClose } = - useAttachedMenu(triggerRef); + const { + open, + x, + y, + handleClick: hookOnClick, + handleMousedown, + handleClose, + } = useAttachedMenu(triggerRef); + + function handleTriggerClick() { + if (triggerRef.current) { + hookOnClick(); + } + } const containerClasses = classNames( className, `${prefix}--overflow-menu__container` ); + const menuClasses = classNames(`${prefix}--overflow-menu__${menuAlignment}`); + const triggerClasses = classNames( `${prefix}--overflow-menu`, { @@ -62,7 +77,7 @@ const OverflowMenu = React.forwardRef(function OverflowMenu( aria-haspopup aria-expanded={open} className={triggerClasses} - onClick={handleClick} + onClick={handleTriggerClick} onMouseDown={handleMousedown} ref={triggerRef} label={label} @@ -70,6 +85,9 @@ const OverflowMenu = React.forwardRef(function OverflowMenu(