[MM-55270] fix(accessibility): "channels" button in global header is not selectable with keyboard (#29224)

Этот коммит содержится в:
Vicktor
2025-01-23 20:27:13 +03:00
коммит произвёл GitHub
родитель d345e92136
Коммит 09fedc6cca
6 изменённых файлов: 154 добавлений и 50 удалений

Просмотреть файл

@@ -11,12 +11,22 @@ exports[`components/global/product_switcher should have an active button state w
onClick={[Function]} onClick={[Function]}
> >
<ProductMenuButton <ProductMenuButton
active={true}
aria-controls="product-switcher-menu" aria-controls="product-switcher-menu"
aria-expanded={true} aria-expanded={true}
aria-label="Product switch menu" aria-label="Product switch menu"
/> style={
<ProductBranding /> Object {
"backgroundColor": "rgba(var(--sidebar-text-rgb), 0.16)",
"color": "rgba(var(--sidebar-text-rgb), 0.56)",
}
}
>
<ProductsIcon
color="rgba(var(--sidebar-text-rgb), 0.56)"
size={20}
/>
<ProductBranding />
</ProductMenuButton>
</ProductMenuContainer> </ProductMenuContainer>
<Menu <Menu
ariaLabel="switcherOpen" ariaLabel="switcherOpen"
@@ -75,12 +85,22 @@ exports[`components/global/product_switcher should match snapshot 1`] = `
onClick={[Function]} onClick={[Function]}
> >
<ProductMenuButton <ProductMenuButton
active={true}
aria-controls="product-switcher-menu" aria-controls="product-switcher-menu"
aria-expanded={true} aria-expanded={true}
aria-label="Product switch menu" aria-label="Product switch menu"
/> style={
<ProductBranding /> Object {
"backgroundColor": "rgba(var(--sidebar-text-rgb), 0.16)",
"color": "rgba(var(--sidebar-text-rgb), 0.56)",
}
}
>
<ProductsIcon
color="rgba(var(--sidebar-text-rgb), 0.56)"
size={20}
/>
<ProductBranding />
</ProductMenuButton>
</ProductMenuContainer> </ProductMenuContainer>
<Menu <Menu
ariaLabel="switcherOpen" ariaLabel="switcherOpen"
@@ -139,12 +159,22 @@ exports[`components/global/product_switcher should match snapshot with product s
onClick={[Function]} onClick={[Function]}
> >
<ProductMenuButton <ProductMenuButton
active={true}
aria-controls="product-switcher-menu" aria-controls="product-switcher-menu"
aria-expanded={true} aria-expanded={true}
aria-label="Product switch menu" aria-label="Product switch menu"
/> style={
<ProductBranding /> Object {
"backgroundColor": "rgba(var(--sidebar-text-rgb), 0.16)",
"color": "rgba(var(--sidebar-text-rgb), 0.56)",
}
}
>
<ProductsIcon
color="rgba(var(--sidebar-text-rgb), 0.56)"
size={20}
/>
<ProductBranding />
</ProductMenuButton>
</ProductMenuContainer> </ProductMenuContainer>
<Menu <Menu
ariaLabel="switcherOpen" ariaLabel="switcherOpen"
@@ -203,12 +233,22 @@ exports[`components/global/product_switcher should match snapshot without licens
onClick={[Function]} onClick={[Function]}
> >
<ProductMenuButton <ProductMenuButton
active={true}
aria-controls="product-switcher-menu" aria-controls="product-switcher-menu"
aria-expanded={true} aria-expanded={true}
aria-label="Product switch menu" aria-label="Product switch menu"
/> style={
<ProductBrandingTeamEdition /> Object {
"backgroundColor": "rgba(var(--sidebar-text-rgb), 0.16)",
"color": "rgba(var(--sidebar-text-rgb), 0.56)",
}
}
>
<ProductsIcon
color="rgba(var(--sidebar-text-rgb), 0.56)"
size={20}
/>
<ProductBrandingTeamEdition />
</ProductMenuButton>
</ProductMenuContainer> </ProductMenuContainer>
<Menu <Menu
ariaLabel="switcherOpen" ariaLabel="switcherOpen"
@@ -267,12 +307,22 @@ exports[`components/global/product_switcher should render once when there are no
onClick={[Function]} onClick={[Function]}
> >
<ProductMenuButton <ProductMenuButton
active={true}
aria-controls="product-switcher-menu" aria-controls="product-switcher-menu"
aria-expanded={true} aria-expanded={true}
aria-label="Product switch menu" aria-label="Product switch menu"
/> style={
<ProductBranding /> Object {
"backgroundColor": "rgba(var(--sidebar-text-rgb), 0.16)",
"color": "rgba(var(--sidebar-text-rgb), 0.56)",
}
}
>
<ProductsIcon
color="rgba(var(--sidebar-text-rgb), 0.56)"
size={20}
/>
<ProductBranding />
</ProductMenuButton>
</ProductMenuContainer> </ProductMenuContainer>
<Menu <Menu
ariaLabel="switcherOpen" ariaLabel="switcherOpen"
@@ -331,12 +381,22 @@ exports[`components/global/product_switcher should render the correct amount of
onClick={[Function]} onClick={[Function]}
> >
<ProductMenuButton <ProductMenuButton
active={true}
aria-controls="product-switcher-menu" aria-controls="product-switcher-menu"
aria-expanded={true} aria-expanded={true}
aria-label="Product switch menu" aria-label="Product switch menu"
/> style={
<ProductBranding /> Object {
"backgroundColor": "rgba(var(--sidebar-text-rgb), 0.16)",
"color": "rgba(var(--sidebar-text-rgb), 0.56)",
}
}
>
<ProductsIcon
color="rgba(var(--sidebar-text-rgb), 0.56)"
size={20}
/>
<ProductBranding />
</ProductMenuButton>
</ProductMenuContainer> </ProductMenuContainer>
<Menu <Menu
ariaLabel="switcherOpen" ariaLabel="switcherOpen"

Просмотреть файл

@@ -2,51 +2,63 @@
exports[`components/ProductBranding should show correct icon glyph when we are on Boards 1`] = ` exports[`components/ProductBranding should show correct icon glyph when we are on Boards 1`] = `
<ProductBrandingContainer <ProductBrandingContainer
tabIndex={0} tabIndex={-1}
> >
<ProductBoardsIcon <ProductBoardsIcon
size={24} size={24}
/> />
<Heading <Heading
className="sr-only"
element="h1" element="h1"
margin="none" margin="none"
size={200} size={200}
> >
Boards Boards
</Heading> </Heading>
<ProductBrandingHeading>
Boards
</ProductBrandingHeading>
</ProductBrandingContainer> </ProductBrandingContainer>
`; `;
exports[`components/ProductBranding should show correct icon glyph when we are on Channels 1`] = ` exports[`components/ProductBranding should show correct icon glyph when we are on Channels 1`] = `
<ProductBrandingContainer <ProductBrandingContainer
tabIndex={0} tabIndex={-1}
> >
<ProductChannelsIcon <ProductChannelsIcon
size={24} size={24}
/> />
<Heading <Heading
className="sr-only"
element="h1" element="h1"
margin="none" margin="none"
size={200} size={200}
> >
Channels Channels
</Heading> </Heading>
<ProductBrandingHeading>
Channels
</ProductBrandingHeading>
</ProductBrandingContainer> </ProductBrandingContainer>
`; `;
exports[`components/ProductBranding should show correct icon glyph when we are on Playbooks 1`] = ` exports[`components/ProductBranding should show correct icon glyph when we are on Playbooks 1`] = `
<ProductBrandingContainer <ProductBrandingContainer
tabIndex={0} tabIndex={-1}
> >
<ProductPlaybooksIcon <ProductPlaybooksIcon
size={24} size={24}
/> />
<Heading <Heading
className="sr-only"
element="h1" element="h1"
margin="none" margin="none"
size={200} size={200}
> >
Playbooks Playbooks
</Heading> </Heading>
<ProductBrandingHeading>
Playbooks
</ProductBrandingHeading>
</ProductBrandingContainer> </ProductBrandingContainer>
`; `;

Просмотреть файл

@@ -9,13 +9,22 @@ import glyphMap, {ProductChannelsIcon} from '@mattermost/compass-icons/component
import {useCurrentProduct} from 'utils/products'; import {useCurrentProduct} from 'utils/products';
const ProductBrandingContainer = styled.div` const ProductBrandingContainer = styled.span`
display: flex; display: flex;
align-items: center; align-items: center;
`;
> * + * { // Every style here except for 'margin-left' and 'font-family'is from the deprecated 'Heading' element.
margin-left: 8px; // https://github.com/mattermost/compass-components/blob/362e96a4eb3489efc8c1852652859ef14a51eb64/src/components/heading/Heading.mixins.ts#L9-L74
} const ProductBrandingHeading = styled.span`
font-family: 'Metropolis';
font-size: 16px;
line-height: 24px;
font-weight: bold;
margin: 0;
color: inherit;
margin-left: 8px;
`; `;
const ProductBranding = (): JSX.Element => { const ProductBranding = (): JSX.Element => {
@@ -24,15 +33,22 @@ const ProductBranding = (): JSX.Element => {
const Icon = currentProduct?.switcherIcon ? glyphMap[currentProduct.switcherIcon] : ProductChannelsIcon; const Icon = currentProduct?.switcherIcon ? glyphMap[currentProduct.switcherIcon] : ProductChannelsIcon;
return ( return (
<ProductBrandingContainer tabIndex={0}> <ProductBrandingContainer tabIndex={-1}>
<Icon size={24}/> <Icon size={24}/>
{/* Heading for screen readers since an h1 shouldn't be inside a button */}
<Heading <Heading
element='h1' element='h1'
size={200} size={200}
margin='none' margin='none'
className='sr-only'
> >
{currentProduct ? currentProduct.switcherText : 'Channels'} {currentProduct ? currentProduct.switcherText : 'Channels'}
</Heading> </Heading>
<ProductBrandingHeading>
{currentProduct ? currentProduct.switcherText : 'Channels'}
</ProductBrandingHeading>
</ProductBrandingContainer> </ProductBrandingContainer>
); );
}; };

Просмотреть файл

@@ -6,7 +6,7 @@ import styled from 'styled-components';
import Logo from 'components/common/svg_images_components/logo_dark_blue_svg'; import Logo from 'components/common/svg_images_components/logo_dark_blue_svg';
const ProductBrandingTeamEditionContainer = styled.div` const ProductBrandingTeamEditionContainer = styled.span`
display: flex; display: flex;
align-items: center; align-items: center;
@@ -21,12 +21,10 @@ const StyledLogo = styled(Logo)`
} }
`; `;
const Badge = styled.div` const Badge = styled.span`
display: flex; display: flex;
align-self: center; align-self: center;
padding: 2px 6px; padding: 2px 6px;
position: relative;
top: 1px;
border-radius: var(--radius-s); border-radius: var(--radius-s);
margin-left: 12px; margin-left: 12px;
background: rgba(var(--sidebar-text-rgb), 0.08); background: rgba(var(--sidebar-text-rgb), 0.08);
@@ -40,7 +38,7 @@ const Badge = styled.div`
const ProductBrandingTeamEdition = (): JSX.Element => { const ProductBrandingTeamEdition = (): JSX.Element => {
return ( return (
<ProductBrandingTeamEditionContainer tabIndex={0}> <ProductBrandingTeamEditionContainer tabIndex={-1}>
<StyledLogo <StyledLogo
width={116} width={116}
height={20} height={20}

Просмотреть файл

@@ -159,7 +159,7 @@ describe('components/global/product_switcher', () => {
useStateSpy.mockImplementation(() => [false, setState]); useStateSpy.mockImplementation(() => [false, setState]);
wrapper.find(ProductMenuContainer).simulate('click'); wrapper.find(ProductMenuContainer).simulate('click');
expect(wrapper.find(ProductMenuButton).props().active).toEqual(true); expect(wrapper.find(ProductMenuButton).props()['aria-expanded']).toEqual(true);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });

Просмотреть файл

@@ -6,7 +6,9 @@ import {useIntl} from 'react-intl';
import {useDispatch, useSelector} from 'react-redux'; import {useDispatch, useSelector} from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import IconButton from '@mattermost/compass-components/components/icon-button'; // eslint-disable-line no-restricted-imports import {
ProductsIcon,
} from '@mattermost/compass-icons/components';
import {getLicense} from 'mattermost-redux/selectors/entities/general'; import {getLicense} from 'mattermost-redux/selectors/entities/general';
@@ -41,21 +43,29 @@ export const ProductMenuContainer = styled.nav`
} }
`; `;
export const ProductMenuButton = styled(IconButton).attrs(() => ({ export const ProductMenuButton = styled.button.attrs(() => ({
id: 'product_switch_menu', id: 'product_switch_menu',
icon: 'products', type: 'button',
size: 'sm',
// we currently need this, since not passing a onClick handler is disabling the IconButton
// this is a known issue and is being tracked by UI platform team
// TODO@UI: remove the onClick, when it is not a mandatory prop anymore
onClick: () => {},
inverted: true,
compact: true,
}))` }))`
> i::before { display: flex;
font-size: 20px; align-items: center;
letter-spacing: 20px; background: transparent;
border: none;
border-radius: 4px;
padding: 3px 6px 3px 5px;
&:hover, &:focus {
color: rgba(var(--sidebar-text-rgb), 0.56);
background-color: rgba(var(--sidebar-text-rgb), 0.08);
}
&:active {
color: rgba(var(--sidebar-text-rgb), 0.56);
background-color: rgba(var(--sidebar-text-rgb), 0.16);
}
> * + * {
margin-left: 8px;
} }
`; `;
@@ -110,13 +120,21 @@ const ProductMenu = (): JSX.Element => {
> >
<ProductMenuContainer onClick={handleClick}> <ProductMenuContainer onClick={handleClick}>
<ProductMenuButton <ProductMenuButton
active={switcherOpen}
aria-expanded={switcherOpen} aria-expanded={switcherOpen}
aria-label={formatMessage({id: 'global_header.productSwitchMenu', defaultMessage: 'Product switch menu'})} aria-label={formatMessage({id: 'global_header.productSwitchMenu', defaultMessage: 'Product switch menu'})}
aria-controls='product-switcher-menu' aria-controls='product-switcher-menu'
/> style={switcherOpen ? {
{license.IsLicensed === 'false' && <ProductBrandingTeamEdition/>} backgroundColor: 'rgba(var(--sidebar-text-rgb), 0.16)',
{license.IsLicensed === 'true' && <ProductBranding/>} color: 'rgba(var(--sidebar-text-rgb), 0.56)',
} : {}}
>
<ProductsIcon
size={20}
color='rgba(var(--sidebar-text-rgb), 0.56)'
/>
{license.IsLicensed === 'false' && <ProductBrandingTeamEdition/>}
{license.IsLicensed === 'true' && <ProductBranding/>}
</ProductMenuButton>
</ProductMenuContainer> </ProductMenuContainer>
<Menu <Menu
listId={'product-switcher-menu-dropdown'} listId={'product-switcher-menu-dropdown'}