Skip to content

Commit 1a93feb

Browse files
authored
[WC-2599] Add item description for Gallery (#1577)
2 parents ce7eec0 + ee8e1d2 commit 1a93feb

File tree

11 files changed

+96
-9
lines changed

11 files changed

+96
-9
lines changed

packages/pluggableWidgets/gallery-web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- We added an option to specify item descriptions in the gallery widget, improving accessibility for screen reader users.
12+
913
## [1.13.0] - 2024-11-13
1014

1115
### Fixed

packages/pluggableWidgets/gallery-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@mendix/gallery-web",
33
"widgetName": "Gallery",
4-
"version": "1.14.0",
4+
"version": "1.14.1",
55
"description": "A flexible gallery widget that renders columns, rows and layouts.",
66
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
77
"license": "Apache-2.0",

packages/pluggableWidgets/gallery-web/src/Gallery.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useItemSelectHelper } from "./helpers/useItemSelectHelper";
1313
import { useRootGalleryStore } from "./helpers/useRootGalleryStore";
1414
import { RootGalleryStore } from "./stores/RootGalleryStore";
1515
import { HeaderContainer } from "./components/HeaderContainer";
16+
import { ObjectItem } from "mendix";
1617

1718
interface RootAPI {
1819
rootStore: RootGalleryStore;
@@ -97,6 +98,7 @@ function Container(props: GalleryContainerProps & RootAPI): ReactElement {
9798
}
9899
headerTitle={props.filterSectionTitle?.value}
99100
ariaLabelListBox={props.ariaLabelListBox?.value}
101+
ariaLabelItem={(item: ObjectItem) => props.ariaLabelItem?.get(item).value}
100102
showHeader={showHeader}
101103
hasMoreItems={props.datasource.hasMoreItems ?? false}
102104
items={items}

packages/pluggableWidgets/gallery-web/src/Gallery.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@
184184
<caption>Content description</caption>
185185
<description>Assistive technology will read this upon reaching gallery.</description>
186186
</property>
187+
<property key="ariaLabelItem" type="textTemplate" dataSource="datasource" required="false">
188+
<caption>Item description</caption>
189+
<description>Assistive technology will read this upon reaching each gallery item.</description>
190+
</property>
187191
</propertyGroup>
188192
</propertyGroup>
189193
</properties>

packages/pluggableWidgets/gallery-web/src/components/Gallery.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface GalleryProps<T extends ObjectItem> {
3737
tabletItems: number;
3838
tabIndex?: number;
3939
ariaLabelListBox?: string;
40+
ariaLabelItem?: (item: T) => string | undefined;
4041
preview?: boolean;
4142

4243
// Helpers
@@ -96,6 +97,7 @@ export function Gallery<T extends ObjectItem>(props: GalleryProps<T>): ReactElem
9697
eventsController={props.itemEventsController}
9798
getPosition={props.getPosition}
9899
itemIndex={index}
100+
label={props.ariaLabelItem?.(item)}
99101
/>
100102
))}
101103
</KeyNavProvider>

packages/pluggableWidgets/gallery-web/src/components/ListItem.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ type ListItemProps = Omit<JSX.IntrinsicElements["div"], "ref" | "role"> & {
1616
itemIndex: number;
1717
selectHelper: SelectActionHandler;
1818
preview?: boolean;
19+
label?: string;
1920
};
2021

2122
export function ListItem(props: ListItemProps): ReactElement {
22-
const { eventsController, getPosition, helper, item, itemIndex, selectHelper, ...rest } = props;
23+
const { eventsController, getPosition, helper, item, itemIndex, selectHelper, label, ...rest } = props;
2324
const clickable = helper.hasOnClick(item) || selectHelper.selectionType !== "None";
24-
const ariaProps = getAriaProps(item, selectHelper);
25+
const ariaProps = getAriaProps(item, selectHelper, label);
2526
const { columnIndex, rowIndex } = getPosition(itemIndex);
2627
const keyNavProps = useFocusTargetProps({ columnIndex: columnIndex ?? -1, rowIndex });
2728
const handlers = useMemo(() => eventsController.getProps(item), [eventsController, item]);

packages/pluggableWidgets/gallery-web/src/components/__tests__/Gallery.spec.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Gallery } from "../Gallery";
66
import { ItemHelperBuilder } from "../../utils/builders/ItemHelperBuilder";
77
import { mockProps, mockItemHelperWithAction, setup } from "../../utils/test-utils";
88
import "./__mocks__/intersectionObserverMock";
9+
import { ObjectItem } from "mendix";
910

1011
describe("Gallery", () => {
1112
describe("DOM Structure", () => {
@@ -133,7 +134,7 @@ describe("Gallery", () => {
133134
});
134135

135136
describe("with accessibility properties", () => {
136-
it("renders correctly", () => {
137+
it("renders correctly without items", () => {
137138
const { asFragment } = render(
138139
<Gallery
139140
{...mockProps()}
@@ -146,6 +147,21 @@ describe("Gallery", () => {
146147

147148
expect(asFragment()).toMatchSnapshot();
148149
});
150+
151+
it("renders correctly with items", () => {
152+
const { asFragment } = render(
153+
<Gallery
154+
{...mockProps()}
155+
items={[{ id: "1" } as ObjectItem, { id: "2" } as ObjectItem, { id: "3" } as ObjectItem]}
156+
ariaLabelItem={(item: ObjectItem) => `title for '${item.id}'`}
157+
headerTitle="filter title"
158+
emptyMessageTitle="empty message"
159+
emptyPlaceholderRenderer={renderWrapper => renderWrapper(<span>No items found</span>)}
160+
/>
161+
);
162+
163+
expect(asFragment()).toMatchSnapshot();
164+
});
149165
});
150166

151167
describe("without filters", () => {

packages/pluggableWidgets/gallery-web/src/components/__tests__/__snapshots__/Gallery.spec.tsx.snap

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,60 @@ exports[`Gallery DOM Structure renders correctly with onclick event 1`] = `
115115
</DocumentFragment>
116116
`;
117117

118-
exports[`Gallery with accessibility properties renders correctly 1`] = `
118+
exports[`Gallery with accessibility properties renders correctly with items 1`] = `
119+
<DocumentFragment>
120+
<div
121+
class="widget-gallery my-gallery"
122+
data-focusindex="0"
123+
>
124+
<section
125+
aria-label="filter title"
126+
class="widget-gallery-header widget-gallery-filter"
127+
>
128+
<input />
129+
</section>
130+
<div
131+
class="widget-gallery-content infinite-loading"
132+
>
133+
<div
134+
aria-label="Mock props ListBox aria label"
135+
class="widget-gallery-items widget-gallery-lg-4 widget-gallery-md-3 widget-gallery-sm-2"
136+
role="list"
137+
>
138+
<div
139+
aria-label="title for '1'"
140+
class="widget-gallery-item item-class"
141+
data-position="0,0"
142+
role="listitem"
143+
tabindex="0"
144+
>
145+
Item content
146+
</div>
147+
<div
148+
aria-label="title for '2'"
149+
class="widget-gallery-item item-class"
150+
data-position="1,0"
151+
role="listitem"
152+
tabindex="-1"
153+
>
154+
Item content
155+
</div>
156+
<div
157+
aria-label="title for '3'"
158+
class="widget-gallery-item item-class"
159+
data-position="2,0"
160+
role="listitem"
161+
tabindex="-1"
162+
>
163+
Item content
164+
</div>
165+
</div>
166+
</div>
167+
</div>
168+
</DocumentFragment>
169+
`;
170+
171+
exports[`Gallery with accessibility properties renders correctly without items 1`] = `
119172
<DocumentFragment>
120173
<div
121174
class="widget-gallery my-gallery"

packages/pluggableWidgets/gallery-web/src/features/item-interaction/get-item-aria-props.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@ type ListItemAriaProps = {
77
role: ListItemRole;
88
"aria-selected": boolean | undefined;
99
tabIndex: number | undefined;
10+
"aria-label": string | undefined;
1011
};
1112

12-
export function getAriaProps(item: ObjectItem, helper: SelectActionHandler): ListItemAriaProps {
13+
export function getAriaProps(item: ObjectItem, helper: SelectActionHandler, label?: string): ListItemAriaProps {
1314
if (helper.selectionType === "Single" || helper.selectionType === "Multi") {
1415
return {
1516
role: "option",
1617
"aria-selected": helper.isSelected(item),
17-
tabIndex: 0
18+
tabIndex: 0,
19+
"aria-label": label
1820
};
1921
}
2022

2123
return {
2224
role: "listitem",
2325
"aria-selected": undefined,
24-
tabIndex: undefined
26+
tabIndex: undefined,
27+
"aria-label": label
2528
};
2629
}

packages/pluggableWidgets/gallery-web/src/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<package xmlns="http://www.mendix.com/package/1.0/">
3-
<clientModule name="Gallery" version="1.14.0" xmlns="http://www.mendix.com/clientModule/1.0/">
3+
<clientModule name="Gallery" version="1.14.1" xmlns="http://www.mendix.com/clientModule/1.0/">
44
<widgetFiles>
55
<widgetFile path="Gallery.xml" />
66
</widgetFiles>

0 commit comments

Comments
 (0)