Skip to content

Commit 31f56ef

Browse files
committed
feat: replicate configurable selection count visibility and clear selection to gallery
1 parent 448ecc6 commit 31f56ef

File tree

11 files changed

+282
-162
lines changed

11 files changed

+282
-162
lines changed

packages/modules/data-widgets/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
99
### Changed
1010

1111
- We enhanced datagrid selection UI with responsive container queries and improved layout styling for header and footer components.
12+
- We enhanced gallery selection UI with responsive container queries and improved layout styling for header and footer components to match datagrid implementation.
1213

1314
## [3.6.1] DataWidgets - 2025-10-14
1415

packages/modules/data-widgets/src/themesource/datawidgets/web/_gallery.scss

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,18 @@ $gallery-screen-md: $screen-md;
7373
}
7474

7575
.widget-gallery-filter,
76-
.widget-gallery-empty,
77-
.widget-gallery-pagination {
76+
.widget-gallery-empty {
7877
flex: 1;
7978
}
8079

80+
&-top-bar {
81+
container: widget-gallery-header / inline-size;
82+
}
83+
84+
&-footer {
85+
container: widget-gallery-footer / inline-size;
86+
}
87+
8188
/**
8289
Helper classes
8390
*/
@@ -89,20 +96,30 @@ $gallery-screen-md: $screen-md;
8996
width: inherit;
9097
}
9198

92-
:where(.widget-gallery-footer-controls) {
99+
:where(.widget-gallery-footer-controls, .widget-gallery-top-bar-controls) {
93100
display: flex;
94101
flex-flow: row nowrap;
102+
align-items: center;
95103
}
96104

97-
:where(.widget-gallery-fc-start) {
98-
margin-block: var(--spacing-medium);
99-
padding-inline: var(--spacing-medium);
105+
:where(.widget-gallery-fc-end, .widget-gallery-tb-end) {
106+
display: flex;
107+
justify-content: flex-end;
108+
align-items: center;
100109
}
101110

102-
:where(.widget-gallery-fc-start, .widget-gallery-fc-middle, .widget-gallery-fc-end) {
111+
:where(.widget-gallery-fc-start, .widget-gallery-tb-start, .widget-gallery-fc-end, .widget-gallery-tb-end) {
103112
flex-grow: 1;
104113
flex-basis: 33.33%;
105114
min-height: 20px;
115+
height: 54px;
116+
padding: var(--spacing-small) 0;
117+
}
118+
119+
:where(.widget-gallery-fc-start, .widget-gallery-tb-start) {
120+
padding-inline: var(--spacing-medium);
121+
display: flex;
122+
align-items: center;
106123
}
107124

108125
.widget-gallery-clear-selection {
@@ -113,4 +130,33 @@ $gallery-screen-md: $screen-md;
113130
color: var(--link-color);
114131
padding: 0;
115132
display: inline-block;
133+
134+
&:focus:not(:focus-visible) {
135+
outline: none;
136+
}
137+
138+
&:focus-visible {
139+
outline: 1px solid var(--brand-primary, $brand-primary);
140+
outline-offset: 2px;
141+
}
142+
}
143+
144+
@container widget-gallery-footer (width < 500px) {
145+
.widget-gallery-footer-controls {
146+
flex-direction: column;
147+
:where(.widget-gallery-fc-start, .widget-gallery-fc-end, .widget-gallery-fc-middle) {
148+
width: 100%;
149+
justify-content: center;
150+
}
151+
}
152+
}
153+
154+
@container widget-gallery-header (width < 500px) {
155+
.widget-gallery-top-bar-controls {
156+
flex-direction: column-reverse;
157+
:where(.widget-gallery-tb-start, .widget-gallery-tb-end) {
158+
width: 100%;
159+
justify-content: center;
160+
}
161+
}
116162
}

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 configurable selection count visibility and clear selection button label template for improved row selection management.
12+
913
## [3.6.1] - 2025-10-14
1014

1115
### Fixed

packages/pluggableWidgets/gallery-web/src/Gallery.editorConfig.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ export function getProperties(values: GalleryPreviewProps, defaultProperties: Pr
1919
}
2020

2121
if (values.itemSelection !== "Multi") {
22-
hidePropertiesIn(defaultProperties, values, ["keepSelection"]);
22+
hidePropertiesIn(defaultProperties, values, [
23+
"keepSelection",
24+
"selectionCountPosition",
25+
"clearSelectionButtonLabel"
26+
]);
2327
}
2428

2529
const usePersonalization = values.storeFilters || values.storeSort;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ const Container = observer(function GalleryContainer(props: GalleryContainerProp
9292
getPosition={getPositionCallback}
9393
loadMoreButtonCaption={props.loadMoreButtonCaption?.value}
9494
showRefreshIndicator={rootStore.loaderCtrl.showRefreshIndicator}
95+
selectionCountPosition={props.selectionCountPosition}
9596
/>
9697
);
9798
});

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,22 @@
3737
<caption>Keep selection</caption>
3838
<description>If enabled, selected items will stay selected unless cleared by the user or a Nanoflow.</description>
3939
</property>
40+
<property key="selectionCountPosition" type="enumeration" defaultValue="bottom" required="true">
41+
<caption>Show selection count</caption>
42+
<description />
43+
<enumerationValues>
44+
<enumerationValue key="top">Top</enumerationValue>
45+
<enumerationValue key="bottom">Bottom</enumerationValue>
46+
<enumerationValue key="off">Off</enumerationValue>
47+
</enumerationValues>
48+
</property>
49+
<property key="clearSelectionButtonLabel" type="textTemplate" required="false">
50+
<caption>Clear selection label</caption>
51+
<description>Customize the label of the 'Clear section' button</description>
52+
<translations>
53+
<translation lang="en_US">Clear selection</translation>
54+
</translations>
55+
</property>
4056
<property key="content" type="widgets" dataSource="datasource" required="false">
4157
<caption>Content placeholder</caption>
4258
<description />

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

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { GalleryTopBar } from "./GalleryTopBar";
1414
import { ListBox } from "./ListBox";
1515
import { ListItem } from "./ListItem";
1616

17-
import { PaginationEnum, ShowPagingButtonsEnum } from "typings/GalleryProps";
17+
import { PaginationEnum, SelectionCountPositionEnum, ShowPagingButtonsEnum } from "typings/GalleryProps";
1818
import { LoadMore, LoadMoreButton as LoadMorePreview } from "../components/LoadMore";
1919
import { ItemEventsController } from "../typings/ItemEventsController";
2020
import { SelectionCounter } from "./SelectionCounter";
@@ -45,6 +45,7 @@ export interface GalleryProps<T extends ObjectItem> {
4545
ariaLabelListBox?: string;
4646
ariaLabelItem?: (item: T) => string | undefined;
4747
preview?: boolean;
48+
selectionCountPosition?: SelectionCountPositionEnum;
4849

4950
// Helpers
5051
focusController: FocusTargetController;
@@ -59,35 +60,46 @@ export interface GalleryProps<T extends ObjectItem> {
5960
export function Gallery<T extends ObjectItem>(props: GalleryProps<T>): ReactElement {
6061
const { loadMoreButtonCaption = "Load more" } = props;
6162
const pagination = props.paging ? (
62-
<div className="widget-gallery-pagination">
63-
<Pagination
64-
canNextPage={props.hasMoreItems}
65-
canPreviousPage={props.page !== 0}
66-
gotoPage={(page: number) => props.setPage && props.setPage(() => page)}
67-
nextPage={() => props.setPage && props.setPage(prev => prev + 1)}
68-
numberOfItems={props.numberOfItems}
69-
page={props.page}
70-
pageSize={props.pageSize}
71-
previousPage={() => props.setPage && props.setPage(prev => prev - 1)}
72-
pagination={props.paginationType}
73-
showPagingButtons={props.showPagingButtons}
74-
/>
75-
</div>
63+
<Pagination
64+
canNextPage={props.hasMoreItems}
65+
canPreviousPage={props.page !== 0}
66+
gotoPage={(page: number) => props.setPage && props.setPage(() => page)}
67+
nextPage={() => props.setPage && props.setPage(prev => prev + 1)}
68+
numberOfItems={props.numberOfItems}
69+
page={props.page}
70+
pageSize={props.pageSize}
71+
previousPage={() => props.setPage && props.setPage(prev => prev - 1)}
72+
pagination={props.paginationType}
73+
showPagingButtons={props.showPagingButtons}
74+
/>
7675
) : null;
7776

7877
const showTopPagination =
7978
props.paging && (props.paginationPosition === "top" || props.paginationPosition === "both");
8079
const showBottomPagination =
8180
props.paging && (props.paginationPosition === "bottom" || props.paginationPosition === "both");
8281

82+
const selectionCounter =
83+
!props.preview && props.selectionCountPosition !== "off" ? (
84+
<SelectionCounter location={props.selectionCountPosition} />
85+
) : null;
86+
87+
const showTopSelectionCounter = selectionCounter && props.selectionCountPosition === "top";
88+
const showBottomSelectionCounter = selectionCounter && props.selectionCountPosition === "bottom";
89+
8390
return (
8491
<GalleryRoot
8592
className={props.className}
8693
style={props.style}
8794
selectable={false}
8895
data-focusindex={props.tabIndex || 0}
8996
>
90-
<GalleryTopBar>{showTopPagination && pagination}</GalleryTopBar>
97+
<GalleryTopBar>
98+
<div className="widget-gallery-top-bar-controls">
99+
{showTopSelectionCounter && selectionCounter}
100+
{showTopPagination && <div className="widget-gallery-tb-end">{pagination}</div>}
101+
</div>
102+
</GalleryTopBar>
91103
{props.showHeader && <GalleryHeader aria-label={props.headerTitle}>{props.header}</GalleryHeader>}
92104
{props.showRefreshIndicator ? <RefreshIndicator className="mx-refresh-container-padding" /> : null}
93105
<GalleryContent
@@ -130,14 +142,17 @@ export function Gallery<T extends ObjectItem>(props: GalleryProps<T>): ReactElem
130142
))}
131143
<GalleryFooter>
132144
<div className="widget-gallery-footer-controls">
133-
<div className="widget-gallery-fc-start">{!props.preview && <SelectionCounter />}</div>
134-
{props.paginationType === "loadMore" && (
135-
<div className="widget-gallery-fc-middle">
136-
{props.preview && <LoadMorePreview>{loadMoreButtonCaption}</LoadMorePreview>}{" "}
137-
{!props.preview && <LoadMore>{loadMoreButtonCaption}</LoadMore>}
138-
</div>
139-
)}
140-
<div className="widget-gallery-fc-end">{showBottomPagination && pagination}</div>
145+
{showBottomSelectionCounter && <div className="widget-gallery-fc-start">{selectionCounter}</div>}
146+
147+
<div className="widget-gallery-fc-end">
148+
{showBottomPagination && pagination}
149+
{props.paginationType === "loadMore" &&
150+
(props.preview ? (
151+
<LoadMorePreview>{loadMoreButtonCaption}</LoadMorePreview>
152+
) : (
153+
<LoadMore>{loadMoreButtonCaption}</LoadMore>
154+
))}
155+
</div>
141156
</div>
142157
</GalleryFooter>
143158
</GalleryRoot>

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,34 @@ import { If } from "@mendix/widget-plugin-component-kit/If";
22
import { observer } from "mobx-react-lite";
33
import { useGalleryRootScope } from "../helpers/root-context";
44

5-
export const SelectionCounter = observer(function SelectionCounter() {
5+
type SelectionCounterLocation = "top" | "bottom" | undefined;
6+
7+
export const SelectionCounter = observer(function SelectionCounter({
8+
location
9+
}: {
10+
location?: SelectionCounterLocation;
11+
}) {
612
const { selectionCountStore, itemSelectHelper } = useGalleryRootScope();
713

14+
const containerClass = location === "top" ? "widget-gallery-tb-start" : "widget-gallery-pb-start";
15+
16+
const clearButtonAriaLabel = `${selectionCountStore.clearButtonLabel} (${selectionCountStore.selectedCount} selected)`;
17+
818
return (
919
<If condition={selectionCountStore.displayCount !== ""}>
10-
<span className="widget-gallery-selection-count">{selectionCountStore.displayCount}</span>&nbsp;|&nbsp;
11-
<button className="widget-gallery-clear-selection" onClick={itemSelectHelper.onClearSelection}>
12-
Clear selection
13-
</button>
20+
<div className={containerClass}>
21+
<span className="widget-gallery-selection-count" aria-live="polite" aria-atomic="true">
22+
{selectionCountStore.displayCount}
23+
</span>
24+
&nbsp;|&nbsp;
25+
<button
26+
className="widget-gallery-clear-selection"
27+
onClick={itemSelectHelper.onClearSelection}
28+
aria-label={clearButtonAriaLabel}
29+
>
30+
{selectionCountStore.clearButtonLabel}
31+
</button>
32+
</div>
1433
</If>
1534
);
1635
});

0 commit comments

Comments
 (0)