diff --git a/packages/modules/data-widgets/CHANGELOG.md b/packages/modules/data-widgets/CHANGELOG.md
index 49f6017a3c..ed248754fe 100644
--- a/packages/modules/data-widgets/CHANGELOG.md
+++ b/packages/modules/data-widgets/CHANGELOG.md
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
+### Changed
+
+- We enhanced datagrid selection UI with responsive container queries and improved layout styling for header and footer components.
+- We enhanced gallery selection UI with responsive container queries and improved layout styling for header and footer components to match datagrid implementation.
+
## [3.6.1] DataWidgets - 2025-10-14
### [3.6.1] Datagrid
diff --git a/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss b/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss
index e6ecaea017..48aac22092 100644
--- a/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss
+++ b/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid.scss
@@ -311,7 +311,7 @@ $root: ".widget-datagrid";
justify-content: flex-end;
white-space: nowrap;
align-items: baseline;
- margin: 16px;
+ margin: 0 16px;
color: $pagination-caption-color;
.paging-status {
@@ -397,6 +397,10 @@ $root: ".widget-datagrid";
}
}
+ &-top-bar {
+ container: widget-datagrid-header / inline-size;
+ }
+
&-content {
overflow-x: auto;
}
@@ -409,6 +413,10 @@ $root: ".widget-datagrid";
display: contents;
}
+ &-footer {
+ container: widget-datagrid-footer / inline-size;
+ }
+
&.widget-datagrid-selection-method-click {
.tr.tr-selected .td {
background-color: $grid-selected-row-background;
@@ -517,7 +525,7 @@ $root: ".widget-datagrid";
.widget-datagrid .widget-datagrid-load-more {
display: block !important;
- margin: 0 auto;
+ margin: 0;
}
.infinite-loading.widget-datagrid-grid-body {
@@ -540,21 +548,30 @@ $root: ".widget-datagrid";
grid-column: 1 / -1;
}
-:where(#{$root}-paging-bottom) {
+:where(#{$root}-paging-bottom, #{$root}-padding-top) {
display: flex;
flex-flow: row nowrap;
align-items: center;
}
-:where(#{$root}-pb-start, #{$root}-pb-end, #{$root}-pb-middle) {
+:where(#{$root}-pb-end, #{$root}-tb-end) {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+}
+
+:where(#{$root}-pb-start, #{$root}-tb-start, #{$root}-pb-end, #{$root}-tb-end, #{$root}-pb-middle) {
flex-grow: 1;
flex-basis: 33.33%;
min-height: 20px;
+ height: 54px;
+ padding: var(--spacing-small) 0;
}
-:where(#{$root}-pb-start) {
- margin-block: var(--spacing-medium);
+:where(#{$root}-pb-start, #{$root}-tb-start) {
padding-inline: var(--spacing-medium);
+ display: flex;
+ align-items: center;
}
#{$root}-clear-selection {
@@ -565,6 +582,15 @@ $root: ".widget-datagrid";
color: var(--link-color);
padding: 0;
display: inline-block;
+
+ &:focus:not(:focus-visible) {
+ outline: none;
+ }
+
+ &:focus-visible {
+ outline: 1px solid var(--brand-primary, $brand-primary);
+ outline-offset: 2px;
+ }
}
@keyframes skeleton-loading {
@@ -578,3 +604,23 @@ $root: ".widget-datagrid";
transform: rotate(1turn);
}
}
+
+@container widget-datagrid-footer (width < 500px) {
+ #{$root}-paging-bottom {
+ flex-direction: column;
+ :where(#{$root}-pb-start, #{$root}-pb-end, #{$root}-pb-middle) {
+ width: 100%;
+ justify-content: center;
+ }
+ }
+}
+
+@container widget-datagrid-header (width < 500px) {
+ #{$root}-padding-top {
+ flex-direction: column-reverse;
+ :where(#{$root}-tb-start, #{$root}-tb-end) {
+ width: 100%;
+ justify-content: center;
+ }
+ }
+}
diff --git a/packages/modules/data-widgets/src/themesource/datawidgets/web/_export-alert.scss b/packages/modules/data-widgets/src/themesource/datawidgets/web/_export-alert.scss
index b3e5460690..42883df20c 100644
--- a/packages/modules/data-widgets/src/themesource/datawidgets/web/_export-alert.scss
+++ b/packages/modules/data-widgets/src/themesource/datawidgets/web/_export-alert.scss
@@ -20,7 +20,7 @@
display: flex;
padding: 4px;
&:focus-visible {
- outline: 1px solid $brand-primary;
+ outline: 1px solid var(--brand-primary, $brand-primary);
}
}
}
diff --git a/packages/modules/data-widgets/src/themesource/datawidgets/web/_gallery.scss b/packages/modules/data-widgets/src/themesource/datawidgets/web/_gallery.scss
index 1269d26e6a..e9a49a6fef 100644
--- a/packages/modules/data-widgets/src/themesource/datawidgets/web/_gallery.scss
+++ b/packages/modules/data-widgets/src/themesource/datawidgets/web/_gallery.scss
@@ -73,11 +73,18 @@ $gallery-screen-md: $screen-md;
}
.widget-gallery-filter,
- .widget-gallery-empty,
- .widget-gallery-pagination {
+ .widget-gallery-empty {
flex: 1;
}
+ &-top-bar {
+ container: widget-gallery-header / inline-size;
+ }
+
+ &-footer {
+ container: widget-gallery-footer / inline-size;
+ }
+
/**
Helper classes
*/
@@ -89,20 +96,30 @@ $gallery-screen-md: $screen-md;
width: inherit;
}
-:where(.widget-gallery-footer-controls) {
+:where(.widget-gallery-footer-controls, .widget-gallery-top-bar-controls) {
display: flex;
flex-flow: row nowrap;
+ align-items: center;
}
-:where(.widget-gallery-fc-start) {
- margin-block: var(--spacing-medium);
- padding-inline: var(--spacing-medium);
+:where(.widget-gallery-fc-end, .widget-gallery-tb-end) {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
}
-:where(.widget-gallery-fc-start, .widget-gallery-fc-middle, .widget-gallery-fc-end) {
+:where(.widget-gallery-fc-start, .widget-gallery-tb-start, .widget-gallery-fc-end, .widget-gallery-tb-end) {
flex-grow: 1;
flex-basis: 33.33%;
min-height: 20px;
+ height: 54px;
+ padding: var(--spacing-small) 0;
+}
+
+:where(.widget-gallery-fc-start, .widget-gallery-tb-start) {
+ padding-inline: var(--spacing-medium);
+ display: flex;
+ align-items: center;
}
.widget-gallery-clear-selection {
@@ -113,4 +130,33 @@ $gallery-screen-md: $screen-md;
color: var(--link-color);
padding: 0;
display: inline-block;
+
+ &:focus:not(:focus-visible) {
+ outline: none;
+ }
+
+ &:focus-visible {
+ outline: 1px solid var(--brand-primary, $brand-primary);
+ outline-offset: 2px;
+ }
+}
+
+@container widget-gallery-footer (width < 500px) {
+ .widget-gallery-footer-controls {
+ flex-direction: column;
+ :where(.widget-gallery-fc-start, .widget-gallery-fc-end, .widget-gallery-fc-middle) {
+ width: 100%;
+ justify-content: center;
+ }
+ }
+}
+
+@container widget-gallery-header (width < 500px) {
+ .widget-gallery-top-bar-controls {
+ flex-direction: column-reverse;
+ :where(.widget-gallery-tb-start, .widget-gallery-tb-end) {
+ width: 100%;
+ justify-content: center;
+ }
+ }
}
diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx
index 9321eba3be..f54fe4aaae 100644
--- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx
+++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx
@@ -22,7 +22,7 @@ export function withParentProvidedEnumStore
(
function useEnumFilterAPI(): Result {
const ctx = useFilterAPI();
- const slctAPI = useRef(undefined);
+ const slctAPI = useRef(undefined);
if (ctx.hasError) {
return error(ctx.error);
diff --git a/packages/pluggableWidgets/datagrid-web/CHANGELOG.md b/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
index a859f8820c..9eb1114fff 100644
--- a/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
+++ b/packages/pluggableWidgets/datagrid-web/CHANGELOG.md
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
+### Added
+
+- We added configurable selection count visibility and clear selection button label template for improved row selection management.
+
## [3.6.1] - 2025-10-14
### Fixed
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
index 2139f6870d..abd7f1cfb2 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts
@@ -75,6 +75,7 @@ export function getProperties(
]);
}
});
+
if (values.pagination === "buttons") {
hidePropertyIn(defaultProperties, values, "showNumberOfRows");
} else {
@@ -169,7 +170,11 @@ function hideSelectionProperties(defaultProperties: Properties, values: Datagrid
}
if (itemSelection !== "Multi") {
- hidePropertyIn(defaultProperties, values, "keepSelection");
+ hidePropertiesIn(defaultProperties, values, [
+ "keepSelection",
+ "selectionCountPosition",
+ "clearSelectionButtonLabel"
+ ]);
}
}
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx
index b1171e778d..82dcc6a041 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx
@@ -117,6 +117,7 @@ const Container = observer((props: Props): ReactElement => {
pageSize={props.pageSize}
paginationType={props.pagination}
loadMoreButtonCaption={props.loadMoreButtonCaption?.value}
+ selectionCountPosition={props.selectionCountPosition}
paging={paginationCtrl.showPagination}
pagingPosition={props.pagingPosition}
showPagingButtons={props.showPagingButtons}
diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
index c19285b1cd..afb1d00ec4 100644
--- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
+++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml
@@ -53,6 +53,22 @@
Keep selection
If enabled, selected items will stay selected unless cleared by the user or a Nanoflow.
+
+ Show selection count
+
+
+ Top
+ Bottom
+ Off
+
+
+
+ Clear selection label
+ Customize the label of the 'Clear section' button
+
+ Clear selection
+
+
Loading type
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/CellElement.tsx b/packages/pluggableWidgets/datagrid-web/src/components/CellElement.tsx
index c85651ffdd..4594854cc7 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/CellElement.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/CellElement.tsx
@@ -1,5 +1,5 @@
import classNames from "classnames";
-import { DOMAttributes, forwardRef, JSX, memo, ReactElement, ReactNode } from "react";
+import { ComponentPropsWithoutRef, DOMAttributes, forwardRef, memo, ReactElement, ReactNode } from "react";
import { AlignmentEnum } from "typings/DatagridProps";
export type CellElementProps = {
@@ -14,7 +14,7 @@ export type CellElementProps = {
wrapText?: boolean;
["aria-hidden"]?: boolean;
tabIndex?: number;
-} & Omit;
+} & Omit, "children">;
const component = forwardRef(function CellElement(
{ className, borderTop, clickable, previewAsHidden, wrapText, alignment, tabIndex, ...rest }: CellElementProps,
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/Grid.tsx b/packages/pluggableWidgets/datagrid-web/src/components/Grid.tsx
index 4a4e61d9ec..96948a3bca 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/Grid.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/Grid.tsx
@@ -1,7 +1,7 @@
import classNames from "classnames";
-import { JSX, ReactElement } from "react";
+import { ComponentPropsWithoutRef, ReactElement } from "react";
-type P = Omit;
+type P = Omit, "role">;
export interface GridProps extends P {
className?: string;
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/SelectionCounter.tsx b/packages/pluggableWidgets/datagrid-web/src/components/SelectionCounter.tsx
new file mode 100644
index 0000000000..c39aad32bf
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-web/src/components/SelectionCounter.tsx
@@ -0,0 +1,35 @@
+import { If } from "@mendix/widget-plugin-component-kit/If";
+import { observer } from "mobx-react-lite";
+import { useDatagridRootScope } from "../helpers/root-context";
+
+type SelectionCounterLocation = "top" | "bottom" | undefined;
+
+export const SelectionCounter = observer(function SelectionCounter({
+ location
+}: {
+ location?: SelectionCounterLocation;
+}) {
+ const { selectionCountStore, selectActionHelper } = useDatagridRootScope();
+
+ const containerClass = location === "top" ? "widget-datagrid-tb-start" : "widget-datagrid-pb-start";
+
+ const clearButtonAriaLabel = `${selectionCountStore.clearButtonLabel} (${selectionCountStore.selectedCount} selected)`;
+
+ return (
+
+
+
+ {selectionCountStore.displayCount}
+
+ |
+
+
+
+ );
+});
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx b/packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx
index 70b7ae1aed..5b7644a280 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx
@@ -9,6 +9,7 @@ import {
LoadingTypeEnum,
PaginationEnum,
PagingPositionEnum,
+ SelectionCountPositionEnum,
ShowPagingButtonsEnum
} from "../../typings/DatagridProps";
import { SelectActionHelper } from "../helpers/SelectActionHelper";
@@ -25,6 +26,7 @@ import { WidgetFooter } from "./WidgetFooter";
import { WidgetHeader } from "./WidgetHeader";
import { WidgetRoot } from "./WidgetRoot";
import { WidgetTopBar } from "./WidgetTopBar";
+import { SelectionCounter } from "./SelectionCounter";
export interface WidgetProps {
CellComponent: CellComponent;
@@ -48,6 +50,7 @@ export interface WidgetProps(props: WidgetProps): ReactElemen
headerContent,
headerTitle,
loadMoreButtonCaption,
+ selectionCountPosition,
numberOfItems,
page,
pageSize,
@@ -131,10 +135,11 @@ const Main = observer((props: WidgetProps): ReactElemen
visibleColumns
} = props;
- const { basicData } = useDatagridRootScope();
+ const { basicData, selectionCountStore } = useDatagridRootScope();
const showHeader = !!headerContent;
- const showTopBar = paging && (pagingPosition === "top" || pagingPosition === "both");
+ const showTopBarPagination = paging && (pagingPosition === "top" || pagingPosition === "both");
+ const showFooterPagination = paging && (pagingPosition === "bottom" || pagingPosition === "both");
const pagination = paging ? (
(props: WidgetProps): ReactElemen
/>
) : null;
+ const selectionCount =
+ selectionCountStore.selectedCount > 0 && selectionCountPosition !== "off" && selectionCountPosition ? (
+
+ ) : null;
+
+ const showTopbarSelectionCount = selectionCount && selectionCountPosition === "top";
+ const showFooterSelectionCount = selectionCount && selectionCountPosition === "bottom";
+
const cssGridStyles = gridStyle(visibleColumns, {
selectItemColumn: selectActionHelper.showCheckboxColumn,
visibilitySelectorColumn: columnsHidable
@@ -160,7 +173,10 @@ const Main = observer((props: WidgetProps): ReactElemen
return (
- {showTopBar && {pagination}}
+
{showHeader && {headerContent}}
(props: WidgetProps): ReactElemen
number) => void;
-} & JSX.IntrinsicElements["div"];
+} & ComponentPropsWithoutRef<"div">;
export function WidgetFooter(props: WidgetFooterProps): ReactElement | null {
- const { pagingPosition, pagination, paginationType, loadMoreButtonCaption, hasMoreItems, setPage, ...rest } = props;
+ const { pagination, selectionCount, paginationType, loadMoreButtonCaption, hasMoreItems, setPage, ...rest } = props;
+
return (
-
-
-
- {hasMoreItems && paginationType === "loadMore" && (
-
+ {selectionCount}
+
+ {pagination}
+ {hasMoreItems && paginationType === "loadMore" && (
-
- )}
-
- {(pagingPosition === "bottom" || pagingPosition === "both") && pagination}
+ )}
);
}
-
-const SelectionCounter = observer(function SelectionCounter() {
- const { selectionCountStore, selectActionHelper } = useDatagridRootScope();
-
- return (
-
- {selectionCountStore.displayCount} |
-
-
- );
-});
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeader.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeader.tsx
index 7e963c1857..f55c0db551 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeader.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeader.tsx
@@ -1,8 +1,8 @@
-import { JSX, ReactElement } from "react";
+import { ComponentPropsWithoutRef, ReactElement } from "react";
type WidgetHeaderProps = {
headerTitle?: string;
-} & JSX.IntrinsicElements["div"];
+} & ComponentPropsWithoutRef<"div">;
export function WidgetHeader(props: WidgetHeaderProps): ReactElement | null {
const { children, headerTitle, ...rest } = props;
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetRoot.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetRoot.tsx
index b3a6b53de3..5f993efc2e 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetRoot.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetRoot.tsx
@@ -1,8 +1,8 @@
import classNames from "classnames";
-import { JSX, ReactElement, useMemo, useRef } from "react";
+import { ComponentPropsWithoutRef, ReactElement, useMemo, useRef } from "react";
import { SelectionMethod } from "../helpers/SelectActionHelper";
-type P = JSX.IntrinsicElements["div"];
+type P = ComponentPropsWithoutRef<"div">;
export interface WidgetRootProps extends P {
className?: string;
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx
index 284e5fd6a3..bfe5b5d46d 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetTopBar.tsx
@@ -1,9 +1,19 @@
-import { JSX, ReactElement } from "react";
+import { ComponentPropsWithoutRef, ReactElement, ReactNode } from "react";
+
+type WidgetTopBarProps = {
+ pagination: ReactNode;
+ selectionCount: ReactNode;
+} & ComponentPropsWithoutRef<"div">;
+
+export function WidgetTopBar(props: WidgetTopBarProps): ReactElement {
+ const { pagination, selectionCount, ...rest } = props;
-export function WidgetTopBar(props: JSX.IntrinsicElements["div"]): ReactElement {
return (
-
- {props.children}
+
+
+ {selectionCount}
+ {pagination &&
{pagination}
}
+
);
}
diff --git a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/Table.spec.tsx.snap b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/Table.spec.tsx.snap
index cdc8ba6713..9b26eb9fe5 100644
--- a/packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/Table.spec.tsx.snap
+++ b/packages/pluggableWidgets/datagrid-web/src/components/__tests__/__snapshots__/Table.spec.tsx.snap
@@ -5,6 +5,13 @@ exports[`Table renders the structure correctly 1`] = `