Skip to content

Commit 1387e11

Browse files
[WC-2838]: Implement RefreshIndicator component on Datagrid 2 (#1765)
2 parents 766e403 + f397317 commit 1387e11

17 files changed

Lines changed: 284 additions & 74 deletions

File tree

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,86 @@ $root: ".widget-datagrid";
429429
display: contents;
430430
}
431431

432+
&-refresh-container {
433+
grid-column: 1 / -1;
434+
padding: 0;
435+
position: relative;
436+
}
437+
438+
&-refresh-indicator {
439+
-webkit-appearance: none;
440+
-moz-appearance: none;
441+
appearance: none;
442+
background-color: var(--border-color-default, #ced0d3);
443+
border: none;
444+
border-radius: 2px;
445+
color: var(--brand-primary, $dg-brand-primary);
446+
height: 4px;
447+
width: 100%;
448+
position: absolute;
449+
left: 0;
450+
right: 0;
451+
452+
&::-webkit-progress-bar {
453+
background-color: transparent;
454+
}
455+
456+
&::-webkit-progress-value {
457+
background-color: currentColor;
458+
transition: all 0.2s;
459+
}
460+
461+
&::-moz-progress-bar {
462+
background-color: currentColor;
463+
transition: all 0.2s;
464+
}
465+
466+
&::-ms-fill {
467+
border: none;
468+
background-color: currentColor;
469+
transition: all 0.2s;
470+
}
471+
472+
&:indeterminate {
473+
background-size: 200% 100%;
474+
background-image: linear-gradient(
475+
to right,
476+
transparent 50%,
477+
currentColor 50%,
478+
currentColor 60%,
479+
transparent 60%,
480+
transparent 71.5%,
481+
currentColor 71.5%,
482+
currentColor 84%,
483+
transparent 84%
484+
);
485+
animation: progress-linear 3s infinite linear;
486+
}
487+
488+
&:indeterminate::-moz-progress-bar {
489+
background-color: transparent;
490+
}
491+
492+
&:indeterminate::-ms-fill {
493+
animation-name: none;
494+
}
495+
496+
@keyframes progress-linear {
497+
0% {
498+
background-size: 200% 100%;
499+
background-position: left -31.25% top 0%;
500+
}
501+
50% {
502+
background-size: 800% 100%;
503+
background-position: left -49% top 0%;
504+
}
505+
100% {
506+
background-size: 400% 100%;
507+
background-position: left -102% top 0%;
508+
}
509+
}
510+
}
511+
432512
&.widget-datagrid-selection-method-click {
433513
.tr.tr-selected .td {
434514
background-color: $dg-grid-selected-row-background;

packages/pluggableWidgets/datagrid-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 implemented a new property to show a refresh indicator. With the refresh indicator, any datasource change shows a progress bar on top of Datagrid 2.
12+
913
## [3.2.0] - 2025-08-18
1014

1115
### Changed

packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,12 @@ export function preview(props: DatagridPreviewProps): ReactElement {
141141
cellEventsController={eventsController}
142142
checkboxEventsController={eventsController}
143143
focusController={focusController}
144+
isFirstLoad={false}
144145
isLoading={false}
145146
isFetchingNextBatch={false}
146147
loadingType="spinner"
147148
columnsLoading={false}
149+
showRefreshIndicator={false}
148150
/>
149151
);
150152
}

packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1+
import { useOnResetFiltersEvent } from "@mendix/widget-plugin-external-events/hooks";
2+
import { useClickActionHelper } from "@mendix/widget-plugin-grid/helpers/ClickActionHelper";
3+
import { useFocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetController";
14
import { useSelectionHelper } from "@mendix/widget-plugin-grid/selection";
25
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
6+
import { observer } from "mobx-react-lite";
37
import { ReactElement, ReactNode, createElement, useCallback, useMemo } from "react";
48
import { DatagridContainerProps } from "../typings/DatagridProps";
59
import { Cell } from "./components/Cell";
610
import { Widget } from "./components/Widget";
711
import { WidgetHeaderContext } from "./components/WidgetHeaderContext";
8-
import { useSelectActionHelper } from "./helpers/SelectActionHelper";
9-
import { useClickActionHelper } from "@mendix/widget-plugin-grid/helpers/ClickActionHelper";
12+
import { ProgressStore } from "./features/data-export/ProgressStore";
13+
import { useDataExport } from "./features/data-export/useDataExport";
1014
import { useCellEventsController } from "./features/row-interaction/CellEventsController";
1115
import { useCheckboxEventsController } from "./features/row-interaction/CheckboxEventsController";
12-
import { useFocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetController";
13-
import { useOnResetFiltersEvent } from "@mendix/widget-plugin-external-events/hooks";
16+
import { useSelectActionHelper } from "./helpers/SelectActionHelper";
1417
import { IColumnGroupStore } from "./helpers/state/ColumnGroupStore";
15-
import { observer } from "mobx-react-lite";
1618
import { RootGridStore } from "./helpers/state/RootGridStore";
1719
import { useRootStore } from "./helpers/state/useRootStore";
18-
import { useDataExport } from "./features/data-export/useDataExport";
19-
import { ProgressStore } from "./features/data-export/ProgressStore";
2020

2121
interface Props extends DatagridContainerProps {
2222
columnsStore: IColumnGroupStore;
@@ -118,10 +118,12 @@ const Container = observer((props: Props): ReactElement => {
118118
cellEventsController={cellEventsController}
119119
checkboxEventsController={checkboxEventsController}
120120
focusController={focusController}
121-
isLoading={rootStore.loaderCtrl.isLoading}
121+
isFirstLoad={rootStore.loaderCtrl.isFirstLoad}
122122
isFetchingNextBatch={rootStore.loaderCtrl.isFetchingNextBatch}
123+
isLoading={props.datasource.status === "loading"}
123124
loadingType={props.loadingType}
124125
columnsLoading={!columnsStore.loaded}
126+
showRefreshIndicator={rootStore.loaderCtrl.showRefreshIndicator}
125127
/>
126128
);
127129
});

packages/pluggableWidgets/datagrid-web/src/Datagrid.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@
5757
<enumerationValue key="skeleton">Skeleton</enumerationValue>
5858
</enumerationValues>
5959
</property>
60+
<property key="refreshIndicator" type="boolean" defaultValue="false">
61+
<caption>Show refresh indicator</caption>
62+
<description>Show a refresh indicator when the data is being loaded.</description>
63+
</property>
6064
</propertyGroup>
6165
<propertyGroup caption="Columns">
6266
<property key="columns" type="object" isList="true">

packages/pluggableWidgets/datagrid-web/src/components/GridBody.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface Props {
88
className?: string;
99
children?: React.ReactNode;
1010
loadingType: LoadingTypeEnum;
11-
isLoading: boolean;
11+
isFirstLoad: boolean;
1212
isFetchingNextBatch?: boolean;
1313
columnsHidable: boolean;
1414
columnsSize: number;
@@ -20,7 +20,7 @@ export function GridBody(props: Props): ReactElement {
2020
const { children } = props;
2121

2222
const content = (): React.ReactElement => {
23-
if (props.isLoading) {
23+
if (props.isFirstLoad) {
2424
return <Loader {...props} rowsSize={props.rowsSize > 0 ? props.rowsSize : props.pageSize} />;
2525
}
2626
return (
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createElement, ReactElement } from "react";
2+
3+
export function RefreshIndicator(): ReactElement {
4+
return (
5+
<div className="tr" role="row">
6+
<div className="th widget-datagrid-refresh-container">
7+
<progress className="widget-datagrid-refresh-indicator" />
8+
</div>
9+
</div>
10+
);
11+
}

packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { FocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navig
2525
import { observer } from "mobx-react-lite";
2626
import { RowsRenderer } from "./RowsRenderer";
2727
import { GridHeader } from "./GridHeader";
28+
import { RefreshIndicator } from "./RefreshIndicator";
2829

2930
export interface WidgetProps<C extends GridColumn, T extends ObjectItem = ObjectItem> {
3031
CellComponent: CellComponent<C>;
@@ -65,10 +66,12 @@ export interface WidgetProps<C extends GridColumn, T extends ObjectItem = Object
6566
cancelExportLabel?: string;
6667
selectRowLabel?: string;
6768
selectAllRowsLabel?: string;
69+
isFirstLoad: boolean;
6870
isLoading: boolean;
6971
isFetchingNextBatch: boolean;
7072
loadingType: LoadingTypeEnum;
7173
columnsLoading: boolean;
74+
showRefreshIndicator: boolean;
7275

7376
// Helpers
7477
cellEventsController: EventsController;
@@ -131,6 +134,7 @@ const Main = observer(<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
131134
paging,
132135
pagingPosition,
133136
preview,
137+
showRefreshIndicator,
134138
selectActionHelper,
135139
setPage,
136140
visibleColumns
@@ -189,8 +193,9 @@ const Main = observer(<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
189193
isLoading={props.columnsLoading}
190194
preview={props.preview}
191195
/>
196+
{showRefreshIndicator ? <RefreshIndicator /> : null}
192197
<GridBody
193-
isLoading={props.isLoading}
198+
isFirstLoad={props.isFirstLoad}
194199
isFetchingNextBatch={props.isFetchingNextBatch}
195200
loadingType={props.loadingType}
196201
columnsHidable={columnsHidable}
Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import { computed, makeObservable } from "mobx";
22

33
type DerivedLoaderControllerSpec = {
4+
showSilentRefresh: boolean;
5+
refreshIndicator: boolean;
46
exp: { exporting: boolean };
57
cols: { loaded: boolean };
6-
query: { isFetchingNextBatch: boolean; isLoading: boolean; isRefreshing: boolean };
8+
query: {
9+
isFetchingNextBatch: boolean;
10+
isFirstLoad: boolean;
11+
isRefreshing: boolean;
12+
isSilentRefresh: boolean;
13+
};
714
};
815

916
export class DerivedLoaderController {
1017
constructor(private spec: DerivedLoaderControllerSpec) {
1118
makeObservable(this, {
12-
isLoading: computed,
19+
isFirstLoad: computed,
1320
isFetchingNextBatch: computed,
1421
isRefreshing: computed
1522
});
1623
}
1724

18-
get isLoading(): boolean {
25+
get isFirstLoad(): boolean {
1926
const { cols, exp, query } = this.spec;
2027
if (!cols.loaded) {
2128
return true;
@@ -25,14 +32,28 @@ export class DerivedLoaderController {
2532
return false;
2633
}
2734

28-
return query.isLoading;
35+
return query.isFirstLoad;
2936
}
3037

3138
get isFetchingNextBatch(): boolean {
3239
return this.spec.query.isFetchingNextBatch;
3340
}
3441

3542
get isRefreshing(): boolean {
36-
return this.spec.query.isRefreshing;
43+
const { isSilentRefresh, isRefreshing } = this.spec.query;
44+
45+
if (this.spec.showSilentRefresh) {
46+
return isSilentRefresh || isRefreshing;
47+
}
48+
49+
return !isSilentRefresh && isRefreshing;
50+
}
51+
52+
get showRefreshIndicator(): boolean {
53+
if (!this.spec.refreshIndicator) {
54+
return false;
55+
}
56+
57+
return this.isRefreshing;
3758
}
3859
}

packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ export class RootGridStore extends BaseControllerHost {
8282
this.loaderCtrl = new DerivedLoaderController({
8383
exp: exportCtrl,
8484
cols: this.columnsStore,
85+
showSilentRefresh: props.refreshInterval > 1,
86+
refreshIndicator: props.refreshIndicator,
8587
query
8688
});
8789

0 commit comments

Comments
 (0)