Skip to content

[WC-2953] pagination as in dg #1726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,8 @@ $gallery-screen-md: 768px;
.widget-gallery-item-button {
width: inherit;
}

.widget-gallery-load-more {
display: flex;
justify-content: center;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import {
import { GalleryPreviewProps } from "../typings/GalleryProps";

export function getProperties(values: GalleryPreviewProps, defaultProperties: Properties): Properties {
if (values.pagination !== "buttons") {
hidePropertyIn(defaultProperties, values, "pagingPosition");
}

if (values.showEmptyPlaceholder === "none") {
hidePropertyIn(defaultProperties, values, "emptyPlaceholder");
}
Expand All @@ -22,8 +18,21 @@ export function getProperties(values: GalleryPreviewProps, defaultProperties: Pr
hidePropertiesIn(defaultProperties, values, ["onSelectionChange", "itemSelectionMode"]);
}

// Hide scrolling settings for now.
hidePropertiesIn(defaultProperties, values, ["showPagingButtons", "showTotalCount"]);
/** Pagination */

if (values.pagination === "buttons") {
hidePropertyIn(defaultProperties, values, "showTotalCount");
} else {
hidePropertyIn(defaultProperties, values, "showPagingButtons");

if (values.showTotalCount === false) {
hidePropertyIn(defaultProperties, values, "pagingPosition");
}
}

if (values.pagination !== "loadMore") {
hidePropertyIn(defaultProperties, values, "loadMoreButtonCaption");
}

return defaultProperties;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ function Preview(props: GalleryPreviewProps): ReactElement {
pageSize={props.pageSize ?? numberOfItems}
paging={props.pagination === "buttons"}
paginationPosition={props.pagingPosition}
paginationType={props.pagination}
showPagingButtons={props.showPagingButtons}
showEmptyStatePreview={props.showEmptyPlaceholder === "custom"}
phoneItems={props.phoneItems!}
tabletItems={props.tabletItems!}
Expand Down
18 changes: 11 additions & 7 deletions packages/pluggableWidgets/gallery-web/src/Gallery.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { observer } from "mobx-react-lite";
import { useOnResetFiltersEvent } from "@mendix/widget-plugin-external-events/hooks";
import { useClickActionHelper } from "@mendix/widget-plugin-grid/helpers/ClickActionHelper";
import { useFocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/useFocusTargetController";
import { getColumnAndRowBasedOnIndex, useSelectionHelper } from "@mendix/widget-plugin-grid/selection";
import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
import { observer } from "mobx-react-lite";
import { ReactElement, ReactNode, createElement, useCallback } from "react";
import { GalleryContainerProps } from "../typings/GalleryProps";
import { Gallery as GalleryComponent } from "./components/Gallery";
import { HeaderWidgetsHost } from "./components/HeaderWidgetsHost";
import { useItemEventsController } from "./features/item-interaction/ItemEventsController";
import { GridPositionsProps, useGridPositions } from "./features/useGridPositions";
import { useItemHelper } from "./helpers/ItemHelper";
import { useItemSelectHelper } from "./helpers/useItemSelectHelper";
import { GalleryContext, GalleryRootScope, useGalleryRootScope } from "./helpers/root-context";
import { useGalleryStore } from "./helpers/useGalleryStore";
import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
import { GalleryRootScope, GalleryContext, useGalleryRootScope } from "./helpers/root-context";
import { HeaderWidgetsHost } from "./components/HeaderWidgetsHost";
import { useItemSelectHelper } from "./helpers/useItemSelectHelper";

const Container = observer(function GalleryContainer(props: GalleryContainerProps): ReactElement {
const { rootStore, itemSelectHelper } = useGalleryRootScope();

const items = props.datasource.items ?? [];
const config: GridPositionsProps = {
desktopItems: props.desktopItems,
Expand Down Expand Up @@ -76,17 +77,20 @@ const Container = observer(function GalleryContainer(props: GalleryContainerProp
numberOfItems={props.datasource.totalCount}
page={rootStore.paging.currentPage}
pageSize={props.pageSize}
paging={props.pagination === "buttons"}
paging={rootStore.paging.showPagination}
paginationPosition={props.pagingPosition}
phoneItems={props.phoneItems}
paginationType={props.pagination}
setPage={rootStore.paging.setPage}
showPagingButtons={props.showPagingButtons}
phoneItems={props.phoneItems}
style={props.style}
tabletItems={props.tabletItems}
tabIndex={props.tabIndex}
selectHelper={itemSelectHelper}
itemEventsController={itemEventsController}
focusController={focusController}
getPosition={getPositionCallback}
loadMoreButtonCaption={props.loadMoreButtonCaption?.value}
/>
);
});
Expand Down
29 changes: 20 additions & 9 deletions packages/pluggableWidgets/gallery-web/src/Gallery.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<description />
</property>
</propertyGroup>
<propertyGroup caption="Items">
<propertyGroup caption="Pagination">
<property key="pageSize" type="integer" defaultValue="20">
<caption>Page size</caption>
<description />
Expand All @@ -63,15 +63,12 @@
<enumerationValues>
<enumerationValue key="buttons">Paging buttons</enumerationValue>
<enumerationValue key="virtualScrolling">Virtual scrolling</enumerationValue>
<enumerationValue key="loadMore">Load more</enumerationValue>
</enumerationValues>
</property>
<property key="pagingPosition" type="enumeration" defaultValue="below">
<caption>Position of paging buttons</caption>
<property key="showTotalCount" type="boolean" defaultValue="false">
<caption>Show total count</caption>
<description />
<enumerationValues>
<enumerationValue key="below">Below grid</enumerationValue>
<enumerationValue key="above">Above grid</enumerationValue>
</enumerationValues>
</property>
<property key="showPagingButtons" type="enumeration" defaultValue="always">
<caption>Show paging buttons</caption>
Expand All @@ -81,10 +78,24 @@
<enumerationValue key="auto">Auto</enumerationValue>
</enumerationValues>
</property>
<property key="showTotalCount" type="boolean" defaultValue="false">
<caption>Show total count</caption>
<property key="pagingPosition" type="enumeration" defaultValue="bottom">
<caption>Position of pagination</caption>
<description />
<enumerationValues>
<enumerationValue key="bottom">Below grid</enumerationValue>
<enumerationValue key="top">Above grid</enumerationValue>
<enumerationValue key="both">Both</enumerationValue>
</enumerationValues>
</property>
<property key="loadMoreButtonCaption" type="textTemplate" required="false">
<caption>Load more caption</caption>
<description />
<translations>
<translation lang="en_US">Load More</translation>
</translations>
</property>
</propertyGroup>
<propertyGroup caption="Items">
<property key="showEmptyPlaceholder" type="enumeration" defaultValue="none">
<caption>Empty message</caption>
<description />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { PositionInGrid, SelectActionHandler } from "@mendix/widget-plugin-grid/
import { ObjectItem } from "mendix";
import { createElement, ReactElement, ReactNode } from "react";
import { GalleryItemHelper } from "../typings/GalleryItem";
import { ListBox } from "./ListBox";
import { ListItem } from "./ListItem";
import { GalleryContent } from "./GalleryContent";
import { GalleryFooter } from "./GalleryFooter";
import { GalleryHeader } from "./GalleryHeader";
import { GalleryRoot } from "./GalleryRoot";
import { GalleryTopBar } from "./GalleryTopBar";
import { ListBox } from "./ListBox";
import { ListItem } from "./ListItem";

import { LoadMore, LoadMoreButton as LoadMorePreview } from "src/components/LoadMore";
import { PaginationEnum, ShowPagingButtonsEnum } from "typings/GalleryProps";
import { ItemEventsController } from "../typings/ItemEventsController";

export interface GalleryProps<T extends ObjectItem> {
Expand All @@ -29,7 +31,9 @@ export interface GalleryProps<T extends ObjectItem> {
paging: boolean;
page: number;
pageSize: number;
paginationPosition?: "below" | "above";
paginationPosition?: "top" | "bottom" | "both";
paginationType: PaginationEnum;
showPagingButtons: ShowPagingButtonsEnum;
showEmptyStatePreview?: boolean;
phoneItems: number;
setPage?: (computePage: (prevPage: number) => number) => void;
Expand All @@ -46,9 +50,11 @@ export interface GalleryProps<T extends ObjectItem> {
itemHelper: GalleryItemHelper;
selectHelper: SelectActionHandler;
getPosition: (index: number) => PositionInGrid;
loadMoreButtonCaption?: string;
}

export function Gallery<T extends ObjectItem>(props: GalleryProps<T>): ReactElement {
const { loadMoreButtonCaption = "Load more" } = props;
const pagination = props.paging ? (
<div className="widget-gallery-pagination">
<Pagination
Expand All @@ -60,13 +66,16 @@ export function Gallery<T extends ObjectItem>(props: GalleryProps<T>): ReactElem
page={props.page}
pageSize={props.pageSize}
previousPage={() => props.setPage && props.setPage(prev => prev - 1)}
pagination={props.paging ? "buttons" : "virtualScrolling"}
pagination={props.paginationType}
showPagingButtons={props.showPagingButtons}
/>
</div>
) : null;

const showTopBar = props.paging && props.paginationPosition === "above";
const showFooter = props.paging && props.paginationPosition === "below";
const showTopPagination =
props.paging && (props.paginationPosition === "top" || props.paginationPosition === "both");
const showBottomPagination =
props.paging && (props.paginationPosition === "bottom" || props.paginationPosition === "both");

return (
<GalleryRoot
Expand All @@ -75,7 +84,7 @@ export function Gallery<T extends ObjectItem>(props: GalleryProps<T>): ReactElem
selectable={false}
data-focusindex={props.tabIndex || 0}
>
{showTopBar && <GalleryTopBar>{pagination}</GalleryTopBar>}
<GalleryTopBar>{showTopPagination && pagination}</GalleryTopBar>
{props.showHeader && <GalleryHeader aria-label={props.headerTitle}>{props.header}</GalleryHeader>}
<GalleryContent hasMoreItems={props.hasMoreItems} setPage={props.setPage} isInfinite={!props.paging}>
{props.items.length > 0 && (
Expand Down Expand Up @@ -111,7 +120,15 @@ export function Gallery<T extends ObjectItem>(props: GalleryProps<T>): ReactElem
<div className="empty-placeholder">{children}</div>
</section>
))}
{showFooter && <GalleryFooter>{pagination}</GalleryFooter>}
<GalleryFooter>
{showBottomPagination && pagination}
<div className="widget-gallery-load-more">
{props.preview && props.paginationType === "loadMore" && (
<LoadMorePreview>{loadMoreButtonCaption}</LoadMorePreview>
)}
{!props.preview && <LoadMore>{loadMoreButtonCaption}</LoadMore>}
</div>
</GalleryFooter>
</GalleryRoot>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import cn from "classnames";
import { observer } from "mobx-react-lite";
import { createElement } from "react";
import { useGalleryRootScope } from "src/helpers/root-context";

export function LoadMoreButton(props: JSX.IntrinsicElements["button"]): React.ReactNode {
return (
<button {...props} className={cn("btn btn-primary widget-gallery-load-more-btn", props.className)}>
{props.children}
</button>
);
}

export const LoadMore = observer(function LoadMore(props: { children: React.ReactNode }): React.ReactNode {
const {
rootStore: { paging }
} = useGalleryRootScope();

if (paging.pagination !== "loadMore") {
return null;
}

if (!paging.hasMoreItems) {
return null;
}

return <LoadMoreButton onClick={() => paging.setPage(n => n + 1)}>{props.children}</LoadMoreButton>;
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import "@testing-library/jest-dom";
import { listAction, listExp, setupIntersectionObserverStub } from "@mendix/widget-plugin-test-utils";
import { waitFor, render } from "@testing-library/react";
import "@testing-library/jest-dom";
import { render, waitFor } from "@testing-library/react";
import { ObjectItem } from "mendix";
import { createElement } from "react";
import { Gallery } from "../Gallery";
import { ItemHelperBuilder } from "../../utils/builders/ItemHelperBuilder";
import { mockProps, mockItemHelperWithAction, setup } from "../../utils/test-utils";
import { ObjectItem } from "mendix";
import { mockItemHelperWithAction, mockProps, setup } from "../../utils/test-utils";
import { Gallery } from "../Gallery";

describe("Gallery", () => {
beforeAll(() => {
Expand Down Expand Up @@ -96,7 +96,7 @@ describe("Gallery", () => {
describe("with pagination", () => {
it("renders correctly", () => {
const { asFragment } = render(
<Gallery {...mockProps()} paging paginationPosition="above" numberOfItems={20} hasMoreItems />
<Gallery {...mockProps()} paging paginationPosition="top" numberOfItems={20} hasMoreItems />
);

expect(asFragment()).toMatchSnapshot();
Expand All @@ -108,7 +108,7 @@ describe("Gallery", () => {
<Gallery
{...mockProps()}
paging
paginationPosition="above"
paginationPosition="top"
numberOfItems={20}
hasMoreItems
setPage={setPage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import { GalleryPropsGate, GalleryStore } from "../stores/GalleryStore";

export function useGalleryStore(props: GalleryContainerProps): GalleryStore {
const gate = useGate(props);
const store = useSetup(
() => new GalleryStore({ gate, ...props, showPagingButtons: "auto", showTotalCount: false })
);
const store = useSetup(() => new GalleryStore({ gate, ...props }));
return store;
}

Expand Down
10 changes: 6 additions & 4 deletions packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { createElement } from "react";
import { ClickActionHelper } from "@mendix/widget-plugin-grid/helpers/ClickActionHelper";
import { FocusTargetController } from "@mendix/widget-plugin-grid/keyboard-navigation/FocusTargetController";
import { SelectActionHandler, getColumnAndRowBasedOnIndex } from "@mendix/widget-plugin-grid/selection";
import { PositionController } from "@mendix/widget-plugin-grid/keyboard-navigation/PositionController";
import { VirtualGridLayout } from "@mendix/widget-plugin-grid/keyboard-navigation/VirtualGridLayout";
import { getColumnAndRowBasedOnIndex, SelectActionHandler } from "@mendix/widget-plugin-grid/selection";
import { listAction, objectItems } from "@mendix/widget-plugin-test-utils";
import { render, RenderResult } from "@testing-library/react";
import userEvent, { UserEvent } from "@testing-library/user-event";
import { ObjectItem } from "mendix";
import { createElement } from "react";
import { GalleryProps } from "../components/Gallery";
import { ItemEventsController } from "../features/item-interaction/ItemEventsController";
import { ItemHelper } from "../helpers/ItemHelper";
import { ItemHelperBuilder } from "./builders/ItemHelperBuilder";
import { PositionController } from "@mendix/widget-plugin-grid/keyboard-navigation/PositionController";
import { VirtualGridLayout } from "@mendix/widget-plugin-grid/keyboard-navigation/VirtualGridLayout";

export function setup(jsx: React.ReactElement): { user: UserEvent } & RenderResult {
return {
Expand Down Expand Up @@ -76,6 +76,8 @@ export function mockProps(params: Helpers & Mocks = {}): GalleryProps<ObjectItem
page: 0,
pageSize: 10,
paging: false,
paginationType: "buttons",
showPagingButtons: "always",
phoneItems: 2,
tabletItems: 3,
desktopItems: 4,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import { ActionValue, DynamicValue, ListValue, ListActionValue, ListExpressionVa

export type ItemSelectionModeEnum = "toggle" | "clear";

export type PaginationEnum = "buttons" | "virtualScrolling";

export type PagingPositionEnum = "below" | "above";
export type PaginationEnum = "buttons" | "virtualScrolling" | "loadMore";

export type ShowPagingButtonsEnum = "always" | "auto";

export type PagingPositionEnum = "bottom" | "top" | "both";

export type ShowEmptyPlaceholderEnum = "none" | "custom";

export type OnClickTriggerEnum = "single" | "double";
Expand All @@ -33,9 +33,10 @@ export interface GalleryContainerProps {
phoneItems: number;
pageSize: number;
pagination: PaginationEnum;
pagingPosition: PagingPositionEnum;
showPagingButtons: ShowPagingButtonsEnum;
showTotalCount: boolean;
showPagingButtons: ShowPagingButtonsEnum;
pagingPosition: PagingPositionEnum;
loadMoreButtonCaption?: DynamicValue<string>;
showEmptyPlaceholder: ShowEmptyPlaceholderEnum;
emptyPlaceholder?: ReactNode;
itemClass?: ListExpressionValue<string>;
Expand Down Expand Up @@ -69,9 +70,10 @@ export interface GalleryPreviewProps {
phoneItems: number | null;
pageSize: number | null;
pagination: PaginationEnum;
pagingPosition: PagingPositionEnum;
showPagingButtons: ShowPagingButtonsEnum;
showTotalCount: boolean;
showPagingButtons: ShowPagingButtonsEnum;
pagingPosition: PagingPositionEnum;
loadMoreButtonCaption: string;
showEmptyPlaceholder: ShowEmptyPlaceholderEnum;
emptyPlaceholder: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
itemClass: string;
Expand Down
Loading
Loading