Skip to content

Commit 911cd20

Browse files
authored
feat(Toolbar): add a11yConfig prop (#4611)
Closes #4609
1 parent 73d5065 commit 911cd20

File tree

3 files changed

+76
-5
lines changed

3 files changed

+76
-5
lines changed

packages/main/src/components/Toolbar/OverflowPopover.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { clsx } from 'clsx';
44
import type { Dispatch, FC, ReactElement, ReactNode, Ref, SetStateAction } from 'react';
55
import React, { cloneElement, useEffect, useRef, useState } from 'react';
66
import { createPortal } from 'react-dom';
7-
import { ButtonDesign, PopoverPlacementType } from '../../enums/index.js';
7+
import { ButtonDesign, PopoverPlacementType, PopupAccessibleRole } from '../../enums/index.js';
88
import { OverflowPopoverContext } from '../../internal/OverflowPopoverContext.js';
99
import { useCanRenderPortal } from '../../internal/ssr.js';
1010
import { stopPropagation } from '../../internal/stopPropagation.js';
@@ -16,6 +16,7 @@ import type {
1616
ToggleButtonPropTypes
1717
} from '../../webComponents/index.js';
1818
import { Popover, ToggleButton } from '../../webComponents/index.js';
19+
import type { ToolbarPropTypes } from './index.js';
1920

2021
interface OverflowPopoverProps {
2122
lastVisibleIndex: number;
@@ -28,6 +29,7 @@ interface OverflowPopoverProps {
2829
overflowPopoverRef?: Ref<PopoverDomRef>;
2930
overflowButton?: ReactElement<ToggleButtonPropTypes> | ReactElement<ButtonPropTypes>;
3031
setIsMounted: Dispatch<SetStateAction<boolean>>;
32+
a11yConfig?: ToolbarPropTypes['a11yConfig'];
3133
}
3234

3335
const isPhone = Device.isPhone();
@@ -43,7 +45,8 @@ export const OverflowPopover: FC<OverflowPopoverProps> = (props: OverflowPopover
4345
showMoreText,
4446
overflowButton,
4547
overflowPopoverRef,
46-
setIsMounted
48+
setIsMounted,
49+
a11yConfig
4750
} = props;
4851
const [pressed, setPressed] = useState(false);
4952
const toggleBtnRef = useRef<ToggleButtonDomRef>(null);
@@ -106,6 +109,13 @@ export const OverflowPopover: FC<OverflowPopoverProps> = (props: OverflowPopover
106109

107110
const canRenderPortal = useCanRenderPortal();
108111

112+
const accessibleRole = (() => {
113+
if (a11yConfig?.overflowPopover?.contentRole) {
114+
return PopupAccessibleRole.None;
115+
}
116+
return a11yConfig?.overflowPopover?.role;
117+
})();
118+
109119
return (
110120
<OverflowPopoverContext.Provider value={{ inPopover: true }}>
111121
{overflowButton ? (
@@ -134,8 +144,14 @@ export const OverflowPopover: FC<OverflowPopoverProps> = (props: OverflowPopover
134144
onBeforeOpen={handleBeforeOpen}
135145
onAfterOpen={handleAfterOpen}
136146
hideArrow
147+
accessibleRole={accessibleRole}
137148
>
138-
<div className={classes.popoverContent} ref={overflowContentRef}>
149+
<div
150+
className={classes.popoverContent}
151+
ref={overflowContentRef}
152+
role={a11yConfig?.overflowPopover?.contentRole}
153+
data-component-name="ToolbarOverflowPopoverContent"
154+
>
139155
{children.map((item, index) => {
140156
if (index > lastVisibleIndex && index > numberOfAlwaysVisibleItems - 1) {
141157
// @ts-expect-error: if props is not defined, it doesn't have an id (is not a ReactElement)

packages/main/src/components/Toolbar/Toolbar.cy.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
ToolbarSeparator,
1414
ToolbarSpacer,
1515
ToolbarStyle,
16-
OverflowToolbarToggleButton
16+
OverflowToolbarToggleButton,
17+
PopupAccessibleRole
1718
} from '../..';
1819
import { ButtonDesign, ToolbarDesign } from '../../enums/index.js';
1920
import { cssVarToRgb, cypressPassThroughTestsFactory, mountWithCustomTagName } from '@/cypress/support/utils';
@@ -582,6 +583,38 @@ describe('Toolbar', () => {
582583
cy.get('#3-overflow').should('have.length', 1);
583584
});
584585

586+
it('a11y - role & contentRole', () => {
587+
cy.viewport(100, 500);
588+
cy.mount(
589+
<Toolbar a11yConfig={{ overflowPopover: { role: PopupAccessibleRole.AlertDialog } }}>
590+
<div>Text1</div>
591+
<div>Text2</div>
592+
<Button>Text4</Button>
593+
</Toolbar>
594+
);
595+
cy.get('section[role="alertdialog"]').should('exist');
596+
597+
cy.mount(
598+
<Toolbar a11yConfig={{ overflowPopover: { contentRole: 'menu' } }}>
599+
<div>Text1</div>
600+
<div>Text2</div>
601+
<Button>Text4</Button>
602+
</Toolbar>
603+
);
604+
cy.get('section').should('not.have.attr', 'role');
605+
cy.get('[data-component-name="ToolbarOverflowPopoverContent"]').should('have.attr', 'role', 'menu');
606+
607+
cy.mount(
608+
<Toolbar a11yConfig={{ overflowPopover: { role: PopupAccessibleRole.AlertDialog, contentRole: 'menu' } }}>
609+
<div>Text1</div>
610+
<div>Text2</div>
611+
<Button>Text4</Button>
612+
</Toolbar>
613+
);
614+
cy.get('section').should('not.have.attr', 'role');
615+
cy.get('[data-component-name="ToolbarOverflowPopoverContent"]').should('have.attr', 'role', 'menu');
616+
});
617+
585618
mountWithCustomTagName(Toolbar);
586619
cypressPassThroughTestsFactory(Toolbar);
587620
});

packages/main/src/components/Toolbar/index.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
useSyncRef
99
} from '@ui5/webcomponents-react-base';
1010
import { clsx } from 'clsx';
11-
import type { ElementType, ReactElement, ReactNode, Ref, RefObject } from 'react';
11+
import type { ElementType, HTMLAttributes, ReactElement, ReactNode, Ref, RefObject } from 'react';
1212
import React, {
1313
Children,
1414
cloneElement,
@@ -21,6 +21,7 @@ import React, {
2121
useState
2222
} from 'react';
2323
import { createUseStyles } from 'react-jss';
24+
import type { PopupAccessibleRole } from '../../enums/index.js';
2425
import { ToolbarDesign, ToolbarStyle } from '../../enums/index.js';
2526
import { SHOW_MORE } from '../../i18n/i18n-defaults.js';
2627
import type { CommonProps } from '../../interfaces/index.js';
@@ -87,6 +88,25 @@ export interface ToolbarPropTypes extends Omit<CommonProps, 'onClick' | 'childre
8788
* This can be useful, for example, when wanting to close the popover on click or selection of a child element.
8889
*/
8990
overflowPopoverRef?: Ref<PopoverDomRef>;
91+
/**
92+
* Defines internally used a11y properties.
93+
*
94+
* __Note:__ When setting `contentRole` of the `overflowPopover`, the `role` is set to `"None"`.
95+
*/
96+
a11yConfig?: {
97+
overflowPopover?: {
98+
/**
99+
* Defines the `accessibleRole` of the overflow `Popover`.
100+
*/
101+
role?: PopupAccessibleRole | keyof typeof PopupAccessibleRole;
102+
/**
103+
* Defines the `role` of the content div inside the overflow `Popover`.
104+
*
105+
* __Note:__ When setting `contentRole`, the `role` is set to `"None"`.
106+
*/
107+
contentRole?: HTMLAttributes<HTMLDivElement>['role'];
108+
};
109+
};
90110
/**
91111
* Fired if the `active` prop is set to true and the user clicks or presses Enter/Space on the `Toolbar`.
92112
*/
@@ -127,6 +147,7 @@ const Toolbar = forwardRef<HTMLDivElement, ToolbarPropTypes>((props, ref) => {
127147
onOverflowChange,
128148
overflowPopoverRef,
129149
overflowButton,
150+
a11yConfig,
130151
...rest
131152
} = props;
132153

@@ -357,6 +378,7 @@ const Toolbar = forwardRef<HTMLDivElement, ToolbarPropTypes>((props, ref) => {
357378
showMoreText={showMoreText}
358379
overflowButton={overflowButton}
359380
setIsMounted={setIsPopoverMounted}
381+
a11yConfig={a11yConfig}
360382
>
361383
{flatChildren}
362384
</OverflowPopover>

0 commit comments

Comments
 (0)