mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-55278]: Fixed autofocus on submenu's first element (#29547)
This commit is contained in:
parent
169274b3aa
commit
b2b956c043
@ -129,12 +129,10 @@ test('Post actions tab support', async ({pw, axe}) => {
|
|||||||
// # Press arrow right
|
// # Press arrow right
|
||||||
await channelsPage.postDotMenu.remindMenuItem.press('ArrowRight');
|
await channelsPage.postDotMenu.remindMenuItem.press('ArrowRight');
|
||||||
|
|
||||||
// * Reminder menu should be visible and have focused
|
// * Reminder menu should be visible
|
||||||
channelsPage.postReminderMenu.toBeVisible();
|
expect(channelsPage.postReminderMenu.container).toBeVisible();
|
||||||
await expect(channelsPage.postReminderMenu.container).toBeFocused();
|
|
||||||
|
|
||||||
// * Should move focus to 30 mins after arrow down
|
// * Should have focus on 30 mins after submenu opens
|
||||||
await channelsPage.postReminderMenu.container.press('ArrowDown');
|
|
||||||
expect(await channelsPage.postReminderMenu.thirtyMinsMenuItem).toBeFocused();
|
expect(await channelsPage.postReminderMenu.thirtyMinsMenuItem).toBeFocused();
|
||||||
|
|
||||||
// * Should move focus to 1 hour after arrow down
|
// * Should move focus to 1 hour after arrow down
|
||||||
|
@ -159,13 +159,16 @@ function PostReminderSubmenu(props: Props) {
|
|||||||
leadingElement={<ClockOutlineIcon size={18}/>}
|
leadingElement={<ClockOutlineIcon size={18}/>}
|
||||||
trailingElements={<span className={'dot-menu__item-trailing-icon'}><ChevronRightIcon size={16}/></span>}
|
trailingElements={<span className={'dot-menu__item-trailing-icon'}><ChevronRightIcon size={16}/></span>}
|
||||||
menuId={`remind_post_${props.post.id}-menu`}
|
menuId={`remind_post_${props.post.id}-menu`}
|
||||||
|
subMenuHeader={
|
||||||
|
<h5 className={'dot-menu__post-reminder-menu-header'}>
|
||||||
|
{formatMessage(
|
||||||
|
{
|
||||||
|
id: 'post_info.post_reminder.sub_menu.header',
|
||||||
|
defaultMessage: 'Set a reminder for:',
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</h5>}
|
||||||
>
|
>
|
||||||
<h5 className={'dot-menu__post-reminder-menu-header'}>
|
|
||||||
{formatMessage(
|
|
||||||
{id: 'post_info.post_reminder.sub_menu.header',
|
|
||||||
defaultMessage: 'Set a reminder for:'},
|
|
||||||
)}
|
|
||||||
</h5>
|
|
||||||
{postReminderSubMenuItems}
|
{postReminderSubMenuItems}
|
||||||
</Menu.SubMenu>
|
</Menu.SubMenu>
|
||||||
);
|
);
|
||||||
|
@ -7,12 +7,13 @@
|
|||||||
min-width: 114px;
|
min-width: 114px;
|
||||||
max-width: 496px;
|
max-width: 496px;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
|
padding: 4px 0;
|
||||||
background-color: var(--center-channel-bg);
|
background-color: var(--center-channel-bg);
|
||||||
box-shadow: var(--elevation-4);
|
box-shadow: var(--elevation-4), 0 0 0 1px rgba(var(--center-channel-color-rgb), 0.12) inset;
|
||||||
}
|
}
|
||||||
&.AsSubMenu {
|
&.AsSubMenu {
|
||||||
& .MuiPaper-root {
|
& .MuiPaper-root {
|
||||||
box-shadow: var(--elevation-5);
|
box-shadow: var(--elevation-5), 0 0 0 1px rgba(var(--center-channel-color-rgb), 0.12) inset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ describe('menu click handlers', () => {
|
|||||||
expect(screen.getByText('Open modal from submenu')).toBeInTheDocument();
|
expect(screen.getByText('Open modal from submenu')).toBeInTheDocument();
|
||||||
|
|
||||||
// Press the down arrow once to focus first submenu item and then twice more to select the one we want
|
// Press the down arrow once to focus first submenu item and then twice more to select the one we want
|
||||||
userEvent.keyboard('{arrowdown}{arrowdown}{arrowdown}');
|
userEvent.keyboard('{arrowdown}{arrowdown}');
|
||||||
|
|
||||||
expect(screen.getByText('Open modal from submenu').closest('li')).toHaveFocus();
|
expect(screen.getByText('Open modal from submenu').closest('li')).toHaveFocus();
|
||||||
|
|
||||||
|
@ -309,6 +309,7 @@ export const MenuItemStyled = styled(MuiMenuItem, {
|
|||||||
flexWrap: 'nowrap',
|
flexWrap: 'nowrap',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
color: isRegular ? 'rgba(var(--center-channel-color-rgb), 0.75)' : 'var(--error-text)',
|
color: isRegular ? 'rgba(var(--center-channel-color-rgb), 0.75)' : 'var(--error-text)',
|
||||||
|
marginInlineStart: '24px',
|
||||||
gap: '4px',
|
gap: '4px',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
lineHeight: '16px',
|
lineHeight: '16px',
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import MuiMenu from '@mui/material/Menu';
|
|
||||||
import MuiMenuList from '@mui/material/MenuList';
|
import MuiMenuList from '@mui/material/MenuList';
|
||||||
|
import MuiPopover from '@mui/material/Popover';
|
||||||
import type {PopoverOrigin} from '@mui/material/Popover';
|
import type {PopoverOrigin} from '@mui/material/Popover';
|
||||||
import React, {
|
import React, {
|
||||||
useState,
|
useState,
|
||||||
@ -33,6 +33,8 @@ import {SubMenuContext, useMenuContextValue} from './menu_context';
|
|||||||
import {MenuItem} from './menu_item';
|
import {MenuItem} from './menu_item';
|
||||||
import type {Props as MenuItemProps} from './menu_item';
|
import type {Props as MenuItemProps} from './menu_item';
|
||||||
|
|
||||||
|
import './menu.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: MenuItemProps['id'];
|
id: MenuItemProps['id'];
|
||||||
leadingElement?: MenuItemProps['leadingElement'];
|
leadingElement?: MenuItemProps['leadingElement'];
|
||||||
@ -47,6 +49,7 @@ interface Props {
|
|||||||
menuAriaDescribedBy?: string;
|
menuAriaDescribedBy?: string;
|
||||||
forceOpenOnLeft?: boolean; // Most of the times this is not needed, since submenu position is calculated and placed
|
forceOpenOnLeft?: boolean; // Most of the times this is not needed, since submenu position is calculated and placed
|
||||||
|
|
||||||
|
subMenuHeader?: ReactNode;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +66,7 @@ export function SubMenu(props: Props) {
|
|||||||
menuAriaDescribedBy,
|
menuAriaDescribedBy,
|
||||||
forceOpenOnLeft,
|
forceOpenOnLeft,
|
||||||
children,
|
children,
|
||||||
|
subMenuHeader,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -136,6 +140,7 @@ export function SubMenu(props: Props) {
|
|||||||
menuId,
|
menuId,
|
||||||
menuAriaLabel,
|
menuAriaLabel,
|
||||||
children,
|
children,
|
||||||
|
subMenuHeader,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -166,35 +171,30 @@ export function SubMenu(props: Props) {
|
|||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
<MuiMenu
|
<SubMenuContext.Provider value={providerValue}>
|
||||||
anchorEl={anchorElement}
|
<MuiPopover
|
||||||
open={isSubMenuOpen}
|
anchorEl={anchorElement}
|
||||||
anchorOrigin={originOfAnchorAndTransform.anchorOrigin}
|
open={isSubMenuOpen}
|
||||||
transformOrigin={originOfAnchorAndTransform.transformOrigin}
|
anchorOrigin={originOfAnchorAndTransform.anchorOrigin}
|
||||||
sx={{pointerEvents: 'none'}}
|
transformOrigin={originOfAnchorAndTransform.transformOrigin}
|
||||||
className='menu_menuStyled AsSubMenu'
|
className='menu_menuStyled AsSubMenu'
|
||||||
>
|
|
||||||
{/* This component is needed here to re enable pointer events for the submenu items which we had to disable above as */}
|
|
||||||
{/* pointer turns to default as soon as it leaves the parent menu */}
|
|
||||||
{/* Notice we dont use the below component in menu.tsx */}
|
|
||||||
<MuiMenuList
|
|
||||||
id={menuId}
|
|
||||||
component='ul'
|
|
||||||
aria-label={menuAriaLabel}
|
|
||||||
aria-describedby={menuAriaDescribedBy}
|
|
||||||
className={A11yClassNames.POPUP}
|
|
||||||
onKeyDown={handleSubMenuKeyDown}
|
|
||||||
sx={{
|
|
||||||
pointerEvents: 'auto', // reset pointer events to default from here on
|
|
||||||
paddingTop: 0,
|
|
||||||
paddingBottom: 0,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<SubMenuContext.Provider value={providerValue}>
|
{subMenuHeader}
|
||||||
|
<MuiMenuList
|
||||||
|
id={menuId}
|
||||||
|
aria-label={menuAriaLabel}
|
||||||
|
aria-describedby={menuAriaDescribedBy}
|
||||||
|
className={A11yClassNames.POPUP}
|
||||||
|
onKeyDown={handleSubMenuKeyDown}
|
||||||
|
autoFocusItem={isSubMenuOpen}
|
||||||
|
sx={{
|
||||||
|
py: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</SubMenuContext.Provider>
|
</MuiMenuList>
|
||||||
</MuiMenuList>
|
</MuiPopover>
|
||||||
</MuiMenu>
|
</SubMenuContext.Provider>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -203,6 +203,7 @@ interface SubMenuModalProps {
|
|||||||
menuId: Props['menuId'];
|
menuId: Props['menuId'];
|
||||||
menuAriaLabel?: Props['menuAriaLabel'];
|
menuAriaLabel?: Props['menuAriaLabel'];
|
||||||
children: Props['children'];
|
children: Props['children'];
|
||||||
|
subMenuHeader?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SubMenuModal(props: SubMenuModalProps) {
|
function SubMenuModal(props: SubMenuModalProps) {
|
||||||
@ -224,9 +225,11 @@ function SubMenuModal(props: SubMenuModalProps) {
|
|||||||
className='menuModal'
|
className='menuModal'
|
||||||
>
|
>
|
||||||
<MuiMenuList
|
<MuiMenuList
|
||||||
|
component={'div'}
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
onClick={handleModalClose}
|
onClick={handleModalClose}
|
||||||
>
|
>
|
||||||
|
{props.subMenuHeader}
|
||||||
{props.children}
|
{props.children}
|
||||||
</MuiMenuList>
|
</MuiMenuList>
|
||||||
</GenericModal>
|
</GenericModal>
|
||||||
|
@ -4,7 +4,7 @@ exports[`UserAccountNameMenuItem should not break if no props are passed 1`] = `
|
|||||||
<div>
|
<div>
|
||||||
<li
|
<li
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
class="MuiButtonBase-root-JvZdr dKFJFs MuiButtonBase-root MuiMenuItem-root MuiMenuItem-gutters MuiMenuItem-root-dXqYNm kIRdVO MuiMenuItem-root MuiMenuItem-gutters sc-gswNZR cUFZeQ userAccountMenu_nameMenuItem"
|
class="MuiButtonBase-root-JvZdr dKFJFs MuiButtonBase-root MuiMenuItem-root MuiMenuItem-gutters MuiMenuItem-root-dXqYNm kIRdVO MuiMenuItem-root MuiMenuItem-gutters sc-gswNZR jjvBbU userAccountMenu_nameMenuItem"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
|
@ -196,20 +196,21 @@ export default function UserAccountDndMenuItem(props: Props) {
|
|||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
role='menuitemradio' // Prevents menu item from closing, not a recommended solution
|
|
||||||
aria-checked={props.isStatusDnd}
|
aria-checked={props.isStatusDnd}
|
||||||
trailingElements={trailingElement}
|
trailingElements={trailingElement}
|
||||||
|
subMenuHeader={
|
||||||
|
<h5
|
||||||
|
id='userAccountMenu_dndSubMenuTitle'
|
||||||
|
className='userAccountMenu_dndMenuItem_subMenuTitle'
|
||||||
|
aria-hidden={true}
|
||||||
|
>
|
||||||
|
{formatMessage({
|
||||||
|
id: 'userAccountMenu.dndSubMenu.title',
|
||||||
|
defaultMessage: 'Clear after:',
|
||||||
|
})}
|
||||||
|
</h5>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<h5
|
|
||||||
id='userAccountMenu_dndSubMenuTitle'
|
|
||||||
className='userAccountMenu_dndMenuItem_subMenuTitle'
|
|
||||||
aria-hidden={true}
|
|
||||||
>
|
|
||||||
{formatMessage({
|
|
||||||
id: 'userAccountMenu.dndSubMenu.title',
|
|
||||||
defaultMessage: 'Clear after:',
|
|
||||||
})}
|
|
||||||
</h5>
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
id={DND_SUB_MENU_ITEMS_IDS.DO_NOT_CLEAR}
|
id={DND_SUB_MENU_ITEMS_IDS.DO_NOT_CLEAR}
|
||||||
labels={
|
labels={
|
||||||
|
Loading…
Reference in New Issue
Block a user