From 147db416b5bdeaff255ab95859b0ebce40421080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Mon, 29 Jan 2024 16:16:40 +0100 Subject: [PATCH] [Menu][base-ui] Focus Menu Items on hover (#40755) Co-authored-by: zanivan --- .../menu/MenuIntroduction/css/index.js | 7 +------ .../menu/MenuIntroduction/css/index.tsx | 7 +------ .../menu/MenuIntroduction/system/index.js | 7 +------ .../menu/MenuIntroduction/system/index.tsx | 7 +------ .../menu/MenuIntroduction/tailwind/index.js | 2 +- .../menu/MenuIntroduction/tailwind/index.tsx | 2 +- .../components/menu/MenuSimple/css/index.js | 7 +------ .../components/menu/MenuSimple/css/index.tsx | 7 +------ .../menu/MenuSimple/system/index.js | 7 +------ .../menu/MenuSimple/system/index.tsx | 7 +------ .../menu/MenuSimple/tailwind/index.js | 2 +- .../menu/MenuSimple/tailwind/index.tsx | 2 +- docs/data/base/components/menu/UseMenu.js | 20 +++++-------------- docs/data/base/components/menu/UseMenu.tsx | 20 +++++-------------- .../base/components/menu/WrappedMenuItems.js | 7 +------ .../base/components/menu/WrappedMenuItems.tsx | 7 +------ docs/pages/base-ui/api/menu-item.json | 1 + docs/pages/base-ui/api/use-menu-item.json | 4 ++++ .../static/components-gallery/base-theme.css | 7 +------ .../api-docs-base/menu-item/menu-item.json | 3 +++ .../api-docs/use-menu-item/use-menu-item.json | 6 +++++- packages/mui-base/src/MenuItem/MenuItem.tsx | 6 ++++++ .../mui-base/src/MenuItem/MenuItem.types.ts | 6 ++++++ packages/mui-base/src/useMenu/menuReducer.ts | 5 ++++- .../mui-base/src/useMenuItem/useMenuItem.ts | 9 ++++++++- .../src/useMenuItem/useMenuItem.types.ts | 6 ++++++ 26 files changed, 68 insertions(+), 103 deletions(-) diff --git a/docs/data/base/components/menu/MenuIntroduction/css/index.js b/docs/data/base/components/menu/MenuIntroduction/css/index.js index a57a0205e9a7e8..8dd3f79cbab282 100644 --- a/docs/data/base/components/menu/MenuIntroduction/css/index.js +++ b/docs/data/base/components/menu/MenuIntroduction/css/index.js @@ -111,7 +111,7 @@ function Styles() { border-bottom: none; } - .CustomMenuIntroduction--item.${menuItemClasses.focusVisible} { + .CustomMenuIntroduction--item:focus { outline: 3px solid ${isDarkMode ? cyan[600] : cyan[200]}; background-color: ${isDarkMode ? grey[800] : grey[100]}; color: ${isDarkMode ? grey[300] : grey[900]}; @@ -121,11 +121,6 @@ function Styles() { color: ${isDarkMode ? grey[700] : grey[400]}; } - .CustomMenuIntroduction--item:hover:not(.${menuItemClasses.disabled}) { - background-color: ${isDarkMode ? cyan[800] : cyan[50]}; - color: ${isDarkMode ? grey[300] : grey[900]}; - } - .TriggerButtonIntroduction { font-family: 'IBM Plex Sans', sans-serif; font-weight: 600; diff --git a/docs/data/base/components/menu/MenuIntroduction/css/index.tsx b/docs/data/base/components/menu/MenuIntroduction/css/index.tsx index 00a011e014066e..e899d9d500df1d 100644 --- a/docs/data/base/components/menu/MenuIntroduction/css/index.tsx +++ b/docs/data/base/components/menu/MenuIntroduction/css/index.tsx @@ -111,7 +111,7 @@ function Styles() { border-bottom: none; } - .CustomMenuIntroduction--item.${menuItemClasses.focusVisible} { + .CustomMenuIntroduction--item:focus { outline: 3px solid ${isDarkMode ? cyan[600] : cyan[200]}; background-color: ${isDarkMode ? grey[800] : grey[100]}; color: ${isDarkMode ? grey[300] : grey[900]}; @@ -121,11 +121,6 @@ function Styles() { color: ${isDarkMode ? grey[700] : grey[400]}; } - .CustomMenuIntroduction--item:hover:not(.${menuItemClasses.disabled}) { - background-color: ${isDarkMode ? cyan[800] : cyan[50]}; - color: ${isDarkMode ? grey[300] : grey[900]}; - } - .TriggerButtonIntroduction { font-family: 'IBM Plex Sans', sans-serif; font-weight: 600; diff --git a/docs/data/base/components/menu/MenuIntroduction/system/index.js b/docs/data/base/components/menu/MenuIntroduction/system/index.js index b57e884703335f..96fd20707e6d0b 100644 --- a/docs/data/base/components/menu/MenuIntroduction/system/index.js +++ b/docs/data/base/components/menu/MenuIntroduction/system/index.js @@ -83,7 +83,7 @@ const MenuItem = styled(BaseMenuItem)( border-bottom: none; } - &.${menuItemClasses.focusVisible} { + &:focus { outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; @@ -92,11 +92,6 @@ const MenuItem = styled(BaseMenuItem)( &.${menuItemClasses.disabled} { color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]}; } - - &:hover:not(.${menuItemClasses.disabled}) { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } `, ); diff --git a/docs/data/base/components/menu/MenuIntroduction/system/index.tsx b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx index d3ed96e7fd0bb4..ff3420f6caf4fa 100644 --- a/docs/data/base/components/menu/MenuIntroduction/system/index.tsx +++ b/docs/data/base/components/menu/MenuIntroduction/system/index.tsx @@ -83,7 +83,7 @@ const MenuItem = styled(BaseMenuItem)( border-bottom: none; } - &.${menuItemClasses.focusVisible} { + &:focus { outline: 3px solid ${theme.palette.mode === 'dark' ? blue[600] : blue[200]}; background-color: ${theme.palette.mode === 'dark' ? grey[800] : grey[100]}; color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; @@ -92,11 +92,6 @@ const MenuItem = styled(BaseMenuItem)( &.${menuItemClasses.disabled} { color: ${theme.palette.mode === 'dark' ? grey[700] : grey[400]}; } - - &:hover:not(.${menuItemClasses.disabled}) { - background-color: ${theme.palette.mode === 'dark' ? blue[900] : blue[50]}; - color: ${theme.palette.mode === 'dark' ? blue[100] : blue[900]}; - } `, ); diff --git a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js index 0269a77e2bc4ae..9bdca1841737af 100644 --- a/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js +++ b/docs/data/base/components/menu/MenuIntroduction/tailwind/index.js @@ -119,7 +119,7 @@ const MenuItem = React.forwardRef((props, ref) => { ((props, ref) => { ((props, ref) => | object }" }, diff --git a/docs/pages/base-ui/api/use-menu-item.json b/docs/pages/base-ui/api/use-menu-item.json index 26b5521fcc4c1f..2830f0a82288b3 100644 --- a/docs/pages/base-ui/api/use-menu-item.json +++ b/docs/pages/base-ui/api/use-menu-item.json @@ -5,6 +5,10 @@ "required": true }, "disabled": { "type": { "name": "boolean", "description": "boolean" } }, + "disableFocusOnHover": { + "type": { "name": "boolean", "description": "boolean" }, + "default": "false" + }, "id": { "type": { "name": "string", "description": "string" } }, "label": { "type": { "name": "string", "description": "string" } }, "onClick": { diff --git a/docs/public/static/components-gallery/base-theme.css b/docs/public/static/components-gallery/base-theme.css index 0aede5b603b1a9..a6828107262eae 100644 --- a/docs/public/static/components-gallery/base-theme.css +++ b/docs/public/static/components-gallery/base-theme.css @@ -428,7 +428,7 @@ border-bottom: none; } -.GalleryMenu-item.base--focusVisible { +.GalleryMenu-item:focus { outline: none; box-shadow: var(--shadow-interactive-focus-visible); background-color: var(--bg-soft); @@ -439,11 +439,6 @@ color: var(--font-color-soft); } -.GalleryMenu-item:hover:not(.base--disabled) { - background-color: var(--bg-soft); - color: var(--font-color-soft); -} - .GalleryMenu { z-index: 1; } diff --git a/docs/translations/api-docs-base/menu-item/menu-item.json b/docs/translations/api-docs-base/menu-item/menu-item.json index c2962000a3ef87..929f2a146ecc0d 100644 --- a/docs/translations/api-docs-base/menu-item/menu-item.json +++ b/docs/translations/api-docs-base/menu-item/menu-item.json @@ -2,6 +2,9 @@ "componentDescription": "An unstyled menu item to be used within a Menu.", "propDescriptions": { "disabled": { "description": "If true, the menu item will be disabled." }, + "disableFocusOnHover": { + "description": "If true, the menu item won't receive focus when the mouse moves over it." + }, "label": { "description": "A text representation of the menu item's content. Used for keyboard text navigation matching." }, diff --git a/docs/translations/api-docs/use-menu-item/use-menu-item.json b/docs/translations/api-docs/use-menu-item/use-menu-item.json index 0afd404823a4eb..771ad3967e0326 100644 --- a/docs/translations/api-docs/use-menu-item/use-menu-item.json +++ b/docs/translations/api-docs/use-menu-item/use-menu-item.json @@ -1,6 +1,10 @@ { "hookDescription": "", - "parametersDescriptions": {}, + "parametersDescriptions": { + "disableFocusOnHover": { + "description": "If true, the menu item won't receive focus when the mouse moves over it." + } + }, "returnValueDescriptions": { "disabled": { "description": "If true, the component is disabled." }, "focusVisible": { diff --git a/packages/mui-base/src/MenuItem/MenuItem.tsx b/packages/mui-base/src/MenuItem/MenuItem.tsx index df4f5e9d8cce99..8417b142030bc4 100644 --- a/packages/mui-base/src/MenuItem/MenuItem.tsx +++ b/packages/mui-base/src/MenuItem/MenuItem.tsx @@ -114,6 +114,12 @@ MenuItem.propTypes /* remove-proptypes */ = { * @default false */ disabled: PropTypes.bool, + /** + * If `true`, the menu item won't receive focus when the mouse moves over it. + * + * @default false + */ + disableFocusOnHover: PropTypes.bool, /** * A text representation of the menu item's content. * Used for keyboard text navigation matching. diff --git a/packages/mui-base/src/MenuItem/MenuItem.types.ts b/packages/mui-base/src/MenuItem/MenuItem.types.ts index 80c7b742319838..4383f7ac219385 100644 --- a/packages/mui-base/src/MenuItem/MenuItem.types.ts +++ b/packages/mui-base/src/MenuItem/MenuItem.types.ts @@ -40,6 +40,12 @@ export interface MenuItemOwnProps { * Used for keyboard text navigation matching. */ label?: string; + /** + * If `true`, the menu item won't receive focus when the mouse moves over it. + * + * @default false + */ + disableFocusOnHover?: boolean; } export interface MenuItemSlots { diff --git a/packages/mui-base/src/useMenu/menuReducer.ts b/packages/mui-base/src/useMenu/menuReducer.ts index 56478d8e456975..74ffc00361bbe0 100644 --- a/packages/mui-base/src/useMenu/menuReducer.ts +++ b/packages/mui-base/src/useMenu/menuReducer.ts @@ -11,7 +11,10 @@ export function menuReducer( action: ActionWithContext, MenuActionContext>, ) { if (action.type === ListActionTypes.itemHover) { - return state; + return { + ...state, + highlightedValue: action.item, + }; } const newState = listReducer(state, action); diff --git a/packages/mui-base/src/useMenuItem/useMenuItem.ts b/packages/mui-base/src/useMenuItem/useMenuItem.ts index 68ab53d6199fb9..8a99206fef4c25 100644 --- a/packages/mui-base/src/useMenuItem/useMenuItem.ts +++ b/packages/mui-base/src/useMenuItem/useMenuItem.ts @@ -41,7 +41,13 @@ const FALLBACK_MENU_CONTEXT: DropdownContextValue = { * - [useMenuItem API](https://mui.com/base-ui/react-menu/hooks-api/#use-menu-item) */ export function useMenuItem(params: UseMenuItemParameters): UseMenuItemReturnValue { - const { disabled = false, id: idParam, rootRef: externalRef, label } = params; + const { + disabled = false, + id: idParam, + rootRef: externalRef, + label, + disableFocusOnHover = false, + } = params; const id = useId(idParam); const itemRef = React.useRef(null); @@ -55,6 +61,7 @@ export function useMenuItem(params: UseMenuItemParameters): UseMenuItemReturnVal const { getRootProps: getListRootProps, highlighted } = useListItem({ item: id, + handlePointerOverEvents: !disableFocusOnHover, }); const { index, totalItemCount } = useCompoundItem(id ?? idGenerator, itemMetadata); diff --git a/packages/mui-base/src/useMenuItem/useMenuItem.types.ts b/packages/mui-base/src/useMenuItem/useMenuItem.types.ts index 8fa180882bf93b..8f5231685ec892 100644 --- a/packages/mui-base/src/useMenuItem/useMenuItem.types.ts +++ b/packages/mui-base/src/useMenuItem/useMenuItem.types.ts @@ -26,6 +26,12 @@ export interface UseMenuItemParameters { label?: string; onClick?: React.MouseEventHandler; rootRef: React.Ref; + /** + * If `true`, the menu item won't receive focus when the mouse moves over it. + * + * @default false + */ + disableFocusOnHover?: boolean; } export interface UseMenuItemReturnValue {