Skip to content

Commit a6bc028

Browse files
authored
feat(ui5-avatar): implement accessibilityInfo getter (#12613)
* feat(ui5-avatar): implement accessibilityInfo getter * chore: make type of avatar translatable and return the info object always * chore: fix lint errors * correct type of avatar is returned * fix lint error
1 parent f647269 commit a6bc028

File tree

3 files changed

+87
-3
lines changed

3 files changed

+87
-3
lines changed

packages/main/cypress/specs/Avatar.cy.tsx

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,71 @@ describe("Accessibility", () => {
2121
.should("have.attr", "aria-label", expectedLabel);
2222
});
2323

24+
it("should return correct accessibilityInfo object when avatar is interactive", () => {
25+
const INITIALS = "JD";
26+
const hasPopup = "menu";
27+
const customLabel = "John Doe Avatar";
28+
29+
cy.mount(
30+
<Avatar
31+
id="interactive-info"
32+
initials={INITIALS}
33+
interactive
34+
accessibleName={customLabel}
35+
accessibilityAttributes={{hasPopup}}
36+
></Avatar>
37+
);
38+
39+
cy.get("#interactive-info").then($avatar => {
40+
const avatar = $avatar[0] as any;
41+
42+
// Check accessibilityInfo properties
43+
expect(avatar.accessibilityInfo).to.exist;
44+
expect(avatar.accessibilityInfo.role).to.equal("button");
45+
expect(avatar.accessibilityInfo.type).to.equal("Button");
46+
expect(avatar.accessibilityInfo.description).to.equal(customLabel);
47+
});
48+
});
49+
50+
it("should return correct accessibilityInfo object when avatar is not interactive", () => {
51+
cy.mount(
52+
<Avatar
53+
id="non-interactive-info"
54+
initials="JD"
55+
></Avatar>
56+
);
57+
58+
cy.get("#non-interactive-info").then($avatar => {
59+
const avatar = $avatar[0] as any;
60+
61+
// Check that accessibilityInfo is undefined
62+
expect(avatar.accessibilityInfo).to.exist;
63+
expect(avatar.accessibilityInfo.role).to.equal("img");
64+
expect(avatar.accessibilityInfo.type).to.equal("Image");
65+
expect(avatar.accessibilityInfo.description).to.equal("Avatar JD");
66+
});
67+
});
68+
69+
it("should use default label for accessibilityInfo description when no custom label is provided", () => {
70+
const INITIALS = "AB";
71+
72+
cy.mount(
73+
<Avatar
74+
id="default-label-info"
75+
initials={INITIALS}
76+
interactive
77+
></Avatar>
78+
);
79+
80+
cy.get("#default-label-info").then($avatar => {
81+
const avatar = $avatar[0] as any;
82+
83+
// Check that accessibilityInfo uses the default label format that includes initials
84+
expect(avatar.accessibilityInfo).to.exist;
85+
expect(avatar.accessibilityInfo.description).to.equal(`Avatar ${INITIALS}`);
86+
});
87+
});
88+
2489
it("checks if accessible-name is correctly passed to the icon", () => {
2590
const ACCESSIBLE_NAME = "Supplier Icon";
2691
const ICON_NAME = "supplier";
@@ -500,4 +565,4 @@ describe("Avatar Rendering and Interaction", () => {
500565
cy.get("@clickStub")
501566
.should("have.been.calledOnce");
502567
});
503-
});
568+
});

packages/main/src/Avatar.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
55
import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
66
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
77
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
8-
import type { AccessibilityAttributes } from "@ui5/webcomponents-base/dist/types.js";
8+
import type { AccessibilityAttributes, AriaRole } from "@ui5/webcomponents-base/dist/types.js";
99
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
1010
import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
1111
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
@@ -17,7 +17,11 @@ import type { IAvatarGroupItem } from "./AvatarGroup.js";
1717
// Template
1818
import AvatarTemplate from "./AvatarTemplate.js";
1919

20-
import { AVATAR_TOOLTIP } from "./generated/i18n/i18n-defaults.js";
20+
import {
21+
AVATAR_TOOLTIP,
22+
AVATAR_TYPE_BUTTON,
23+
AVATAR_TYPE_IMAGE,
24+
} from "./generated/i18n/i18n-defaults.js";
2125

2226
// Styles
2327
import AvatarCss from "./generated/themes/Avatar.css.js";
@@ -493,6 +497,15 @@ class Avatar extends UI5Element implements ITabbable, IAvatarGroupItem {
493497
}
494498
this._imageLoadError = true;
495499
}
500+
501+
get accessibilityInfo() {
502+
return {
503+
role: this._role as AriaRole,
504+
type: this.interactive ? Avatar.i18nBundle.getText(AVATAR_TYPE_BUTTON) : Avatar.i18nBundle.getText(AVATAR_TYPE_IMAGE),
505+
description: this.accessibleNameText,
506+
disabled: this.disabled,
507+
};
508+
}
496509
}
497510

498511
Avatar.define();

packages/main/src/i18n/messagebundle.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ ARIA_ROLEDESCRIPTION_INTERACTIVE_CARD_HEADER=Interactive Card Header
1616
#XACT: ARIA announcement for the Avatar default tooltip
1717
AVATAR_TOOLTIP=Avatar
1818

19+
#XACT: ARIA type description for interactive Avatar (when the Avatar is a button)
20+
AVATAR_TYPE_BUTTON=Button
21+
22+
#XACT: ARIA type description for non-interactive Avatar (when the Avatar is an image)
23+
AVATAR_TYPE_IMAGE=Image
24+
1925
#XACT: ARIA announcement for the Avatar default tooltip
2026
AVATAR_GROUP_DISPLAYED_HIDDEN_LABEL={0} displayed, {1} hidden.
2127

0 commit comments

Comments
 (0)