fix(drawer): improve accessibility (aria-labelledby) (#55697)

Co-authored-by: 遇见同学 <1875694521@qq.com>
Co-authored-by: thinkasany <480968828@qq.com>
This commit is contained in:
x1ngYu
2025-11-14 11:40:29 +08:00
committed by GitHub
parent 4e19a1f3d2
commit 19a37a3780
5 changed files with 72 additions and 4 deletions

View File

@@ -21,7 +21,7 @@ export interface DrawerStyles extends NonNullable<RCDrawerProps['styles']> {
export interface DrawerPanelProps {
prefixCls: string;
ariaId?: string;
title?: React.ReactNode;
footer?: React.ReactNode;
extra?: React.ReactNode;
@@ -62,6 +62,7 @@ export interface DrawerPanelProps {
const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
const {
prefixCls,
ariaId,
title,
footer,
extra,
@@ -128,7 +129,11 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
>
<div className={`${prefixCls}-header-title`}>
{closablePlacement === 'start' && mergedCloseIcon}
{title && <div className={`${prefixCls}-title`}>{title}</div>}
{title && (
<div className={`${prefixCls}-title`} id={ariaId}>
{title}
</div>
)}
</div>
{extra && <div className={`${prefixCls}-extra`}>{extra}</div>}
{closablePlacement === 'end' && mergedCloseIcon}

View File

@@ -460,4 +460,25 @@ describe('Drawer', () => {
// 添加快照断言
expect(container.firstChild).toMatchSnapshot();
});
it('should have aria-labelledby on drawer content when title is provided', () => {
const { baseElement, rerender } = render(<Drawer open>Here is content of Drawer</Drawer>);
const content = baseElement.querySelector('.ant-drawer-content');
expect(content).not.toHaveAttribute('aria-labelledby');
rerender(
<Drawer open title="Test Title">
Here is content of Drawer
</Drawer>,
);
const title = baseElement.querySelector('.ant-drawer-title');
expect(content).toHaveAttribute('aria-labelledby', title?.getAttribute('id'));
rerender(
<Drawer open title="Test Title" aria-labelledby="custom-id">
Here is content of Drawer
</Drawer>,
);
expect(content).toHaveAttribute('aria-labelledby', 'custom-id');
});
});

View File

@@ -379,6 +379,7 @@ exports[`Drawer have a title 1`] = `
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -417,6 +418,7 @@ exports[`Drawer have a title 1`] = `
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Test Title
</div>

View File

@@ -28,6 +28,7 @@ Array [
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -66,6 +67,7 @@ Array [
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Basic Drawer
</div>
@@ -146,6 +148,7 @@ Array [
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content acss-10412ne"
role="dialog"
@@ -186,6 +189,7 @@ Array [
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Basic Drawer
</div>
@@ -238,6 +242,7 @@ Array [
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content acss-10412ne"
role="dialog"
@@ -278,6 +283,7 @@ Array [
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Basic Drawer
</div>
@@ -345,6 +351,7 @@ Array [
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -357,6 +364,7 @@ Array [
>
<div
class="ant-drawer-title"
id="test-id"
>
Drawer Closable Placement
</div>
@@ -509,6 +517,7 @@ exports[`renders components/drawer/demo/config-provider.tsx extend context corre
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -547,6 +556,7 @@ exports[`renders components/drawer/demo/config-provider.tsx extend context corre
</button>
<div
class="ant-drawer-title"
id="test-id"
>
ConfigProvider
</div>
@@ -712,6 +722,7 @@ Array [
style="width: 500px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -750,6 +761,7 @@ Array [
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Drawer with extra actions
</div>
@@ -868,6 +880,7 @@ Array [
style="width: 720px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -906,6 +919,7 @@ Array [
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Create a new account
</div>
@@ -2948,6 +2962,7 @@ Array [
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -2986,6 +3001,7 @@ Array [
</button>
<div
class="ant-drawer-title"
id="test-id"
>
<p>
Loading Drawer
@@ -3058,6 +3074,7 @@ Array [
style="width: 520px; transform: translateX(-180px);"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3070,6 +3087,7 @@ Array [
>
<div
class="ant-drawer-title"
id="test-id"
>
Multi-level drawer
</div>
@@ -3105,6 +3123,7 @@ Array [
style="width: 320px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3117,6 +3136,7 @@ Array [
>
<div
class="ant-drawer-title"
id="test-id"
>
Two-level Drawer
</div>
@@ -3176,6 +3196,7 @@ Array [
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3214,6 +3235,7 @@ Array [
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Drawer without mask
</div>
@@ -3379,6 +3401,7 @@ Array [
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3391,6 +3414,7 @@ Array [
>
<div
class="ant-drawer-title"
id="test-id"
>
Basic Drawer
</div>
@@ -3458,6 +3482,7 @@ exports[`renders components/drawer/demo/render-in-current.tsx extend context cor
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3470,6 +3495,7 @@ exports[`renders components/drawer/demo/render-in-current.tsx extend context cor
>
<div
class="ant-drawer-title"
id="test-id"
>
Basic Drawer
</div>
@@ -3697,6 +3723,7 @@ exports[`renders components/drawer/demo/scroll-debug.tsx extend context correctl
style="width: 378px; transform: translateX(-180px);"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3735,6 +3762,7 @@ exports[`renders components/drawer/demo/scroll-debug.tsx extend context correctl
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Drawer
</div>
@@ -3763,6 +3791,7 @@ exports[`renders components/drawer/demo/scroll-debug.tsx extend context correctl
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3801,6 +3830,7 @@ exports[`renders components/drawer/demo/scroll-debug.tsx extend context correctl
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Drawer Sub
</div>
@@ -3848,6 +3878,7 @@ exports[`renders components/drawer/demo/scroll-debug.tsx extend context correctl
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3886,6 +3917,7 @@ exports[`renders components/drawer/demo/scroll-debug.tsx extend context correctl
</button>
<div
class="ant-drawer-title"
id="test-id"
>
Drawer2
</div>
@@ -3958,6 +3990,7 @@ Array [
style="width: 378px;"
>
<div
aria-labelledby="test-id"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
@@ -3996,6 +4029,7 @@ Array [
</button>
<div
class="ant-drawer-title"
id="test-id"
>
undefined Drawer
</div>

View File

@@ -4,6 +4,7 @@ import type { DrawerProps as RcDrawerProps } from 'rc-drawer';
import RcDrawer from 'rc-drawer';
import type { Placement } from 'rc-drawer/lib/Drawer';
import type { CSSMotionProps } from 'rc-motion';
import useId from 'rc-util/lib/hooks/useId';
import { composeRef } from 'rc-util/lib/ref';
import ContextIsolator from '../_util/ContextIsolator';
@@ -28,7 +29,7 @@ export interface PushState {
// Drawer diff props: 'open' | 'motion' | 'maskMotion' | 'wrapperClassName'
export interface DrawerProps
extends Omit<RcDrawerProps, 'maskStyle' | 'destroyOnClose'>,
Omit<DrawerPanelProps, 'prefixCls'> {
Omit<DrawerPanelProps, 'prefixCls' | 'ariaId'> {
size?: sizeType;
open?: boolean;
@@ -70,6 +71,7 @@ const Drawer: React.FC<DrawerProps> & {
panelRef = null,
style,
className,
'aria-labelledby': ariaLabelledby,
// Deprecated
visible,
@@ -82,6 +84,9 @@ const Drawer: React.FC<DrawerProps> & {
...rest
} = props;
let ariaId: string | undefined = useId();
ariaId = rest.title ? ariaId : undefined;
const {
getPopupContainer,
getPrefixCls,
@@ -221,10 +226,11 @@ const Drawer: React.FC<DrawerProps> & {
afterOpenChange={afterOpenChange ?? afterVisibleChange}
panelRef={mergedPanelRef}
zIndex={zIndex}
aria-labelledby={ariaLabelledby ?? ariaId}
// TODO: In the future, destroyOnClose in rc-drawer needs to be upgrade to destroyOnHidden
destroyOnClose={destroyOnHidden ?? destroyOnClose}
>
<DrawerPanel prefixCls={prefixCls} {...rest} onClose={onClose} />
<DrawerPanel prefixCls={prefixCls} {...rest} ariaId={ariaId} onClose={onClose} />
</RcDrawer>
</zIndexContext.Provider>
</ContextIsolator>,