Skip to content
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
7 changes: 7 additions & 0 deletions packages/pluggableWidgets/gallery-native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]

- We've fixed an issue where Gallery widget does not display data.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should be moved under the ### Fixed section.

### Fixed

- We've fixed an issue where items in a horizontally scrolling Gallery would stretch to the full screen width instead of sizing to their content.

### Added

- Added horizontal item sizing options to fit items to their content or divide the Gallery width into configurable columns.

## [2.2.1] - 2026-6-10

Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/gallery-native/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gallery-native",
"widgetName": "Gallery",
"version": "2.2.2",
"version": "2.3.0",
"description": "A flexible gallery widget that renders columns, rows and layouts.",
"copyright": "© Mendix Technology BV 2022. All rights reserved.",
"license": "Apache-2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ export function getProperties(values: GalleryPreviewProps, defaultProperties: Pr
hidePropertyIn(defaultProperties, values, "loadMoreButtonCaption");
}

if (values.scrollDirection === "horizontal") {
if (values.scrollDirection === "vertical") {
hidePropertyIn(defaultProperties, values, "horizontalItemSizing");
} else {
hidePropertyIn(defaultProperties, values, "pullDown");
hidePropertyIn(defaultProperties, values, "tabletColumns");
hidePropertyIn(defaultProperties, values, "phoneColumns");
if (values.horizontalItemSizing !== "fillColumns") {
hidePropertyIn(defaultProperties, values, "tabletColumns");
hidePropertyIn(defaultProperties, values, "phoneColumns");
}
}

if (values.filterList?.length === 0) {
Expand Down
1 change: 1 addition & 0 deletions packages/pluggableWidgets/gallery-native/src/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export const Gallery = (props: GalleryProps<GalleryStyle>): ReactElement => {
pullDown={props.pullDown && pullDown}
pullDownIsExecuting={props.pullDown?.isExecuting ?? false}
scrollDirection={props.scrollDirection}
horizontalItemSizing={props.horizontalItemSizing}
style={styles}
tabletColumns={props.tabletColumns}
/>
Expand Down
9 changes: 9 additions & 0 deletions packages/pluggableWidgets/gallery-native/src/Gallery.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@
<enumerationValue key="horizontal">Horizontal</enumerationValue>
</enumerationValues>
</property>
<property key="horizontalItemSizing" type="enumeration" defaultValue="fullWidth">
<caption>Horizontal item sizing</caption>
<description>Choose whether horizontal items use the full screen width, fit their content, or divide the available Gallery width into columns.</description>
<enumerationValues>
<enumerationValue key="fullWidth">Full width</enumerationValue>
<enumerationValue key="fitContent">Fit content</enumerationValue>
<enumerationValue key="fillColumns">Fill columns</enumerationValue>
</enumerationValues>
</property>
</propertyGroup>
<propertyGroup caption="Columns">
<property key="tabletColumns" type="integer" defaultValue="1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { View } from "react-native";

// Mock FlashList - render items directly without using FlatList
export const FlashList = React.forwardRef((props: any) => {
export const FlashList = React.forwardRef((props: any, ref) => {
const {
data = [],
renderItem,
Expand All @@ -27,7 +27,7 @@ export const FlashList = React.forwardRef((props: any) => {
};

return (
<View {...rest} refreshControl={refreshControl}>
<View ref={ref} {...rest} refreshControl={refreshControl}>
{ListHeaderComponent}
{renderItems()}
{ListFooterComponent}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { ReactElement, ReactNode, useCallback, useMemo, useState } from "react";
import { Text, Pressable, View, ViewProps, Platform, TouchableOpacity, useWindowDimensions } from "react-native";
import {
LayoutChangeEvent,
Text,
Pressable,
View,
ViewProps,
Platform,
TouchableOpacity,
useWindowDimensions
} from "react-native";
import { ObjectItem, DynamicValue } from "mendix";
import DeviceInfo from "react-native-device-info";
import { GalleryStyle } from "../ui/Styles";
import { PaginationEnum, ScrollDirectionEnum } from "../../typings/GalleryProps";
import { HorizontalItemSizingEnum, PaginationEnum, ScrollDirectionEnum } from "../../typings/GalleryProps";
import { isAvailable } from "@mendix/piw-utils-internal";
import { extractStyles } from "@mendix/pluggable-widgets-tools";
import { FlashList } from "@shopify/flash-list";
Expand All @@ -24,6 +33,7 @@ export interface GalleryProps<T extends ObjectItem> {
pullDown?: () => void;
pullDownIsExecuting?: boolean;
scrollDirection: ScrollDirectionEnum;
horizontalItemSizing: HorizontalItemSizingEnum;
style: GalleryStyle;
tabletColumns: number;
}
Expand All @@ -34,10 +44,22 @@ export const Gallery = <T extends ObjectItem>(props: GalleryProps<T>): ReactElem
const firstItemId = props.items?.[0]?.id;
const lastItemId = props.items?.[props.items.length - 1]?.id;
const { name, style, itemRenderer } = props;
const { width, height: windowHeight } = useWindowDimensions();
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
// FlashList is virtualized and only renders items that fit within its own height, so it needs a
// non-zero height. The list area is sized from FlashList's reported content height instead of flex.
const [contentHeight, setContentHeight] = useState(0);
const [containerWidth, setContainerWidth] = useState(0);
const horizontalItemWidth = isScrollDirectionVertical
? undefined
: props.horizontalItemSizing === "fullWidth"
? windowWidth
: props.horizontalItemSizing === "fillColumns" && containerWidth > 0
? containerWidth / numColumns

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This simple division here might need compensation about the container insets/padding/borders.

Specifically for cases of custom-styled Galleries, we might need to perhaps subtract the inset values - worth double checking with LLM though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image Image Image

After adding this styling this(2nd image) was the result. I think it handles as expected. What do you think ?

: undefined;

const onContainerLayout = useCallback((event: LayoutChangeEvent): void => {
setContainerWidth(event.nativeEvent.layout.width);
}, []);

const onEndReached = (): void => {
if (props.pagination === "virtualScrolling" && props.hasMoreItems) {
Expand All @@ -48,9 +70,8 @@ export const Gallery = <T extends ObjectItem>(props: GalleryProps<T>): ReactElem
const renderItem = useCallback(
(item: { item: T }): ReactElement =>
itemRenderer((children, onPress) => {
const itemStyle = isScrollDirectionVertical ? undefined : { width };
const listItemWrapperProps: ViewProps = {
style: itemStyle,
style: horizontalItemWidth === undefined ? undefined : { width: horizontalItemWidth },
testID: `${name}-list-item-${item.item.id}`
};
const renderListItemContent = (
Expand All @@ -74,8 +95,7 @@ export const Gallery = <T extends ObjectItem>(props: GalleryProps<T>): ReactElem
}, item.item),
[
itemRenderer,
isScrollDirectionVertical,
width,
horizontalItemWidth,
name,
style.listItem,
style.firstItem,
Expand Down Expand Up @@ -160,7 +180,13 @@ export const Gallery = <T extends ObjectItem>(props: GalleryProps<T>): ReactElem
const listStyle = isScrollDirectionVertical ? [{ flex: 1 }, props.style.list] : props.style.list;

return (
<View testID={`${name}`} style={containerStyle}>
<View
testID={`${name}`}
style={containerStyle}
{...(!isScrollDirectionVertical && props.horizontalItemSizing === "fillColumns"
? { onLayout: onContainerLayout }
: {})}
>
{props.filters ? <View>{props.filters}</View> : null}
{!hasItems && renderEmptyPlaceholder}
{hasItems ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const defaultProps: GalleryProps<ObjectItem> = {
pagination: "virtualScrolling",
phoneColumns: 2,
scrollDirection: "vertical",
horizontalItemSizing: "fullWidth",
style: { container: {} },
tabletColumns: 3
};
Expand All @@ -46,6 +47,39 @@ describe("Gallery", () => {
expect(gallery).toMatchSnapshot();
});

it("preserves full-width horizontal items by default", () => {
const gallery = render(<Gallery {...defaultProps} scrollDirection="horizontal" />);
const list = gallery.getByTestId("gallery-test-list");
const firstItem = gallery.getByTestId("gallery-test-list-item-11");

expect(list.props.ItemSeparatorComponent).toBeUndefined();
expect(firstItem.props.style).toEqual({ width: 750 });
});

it("does not constrain or separate fit-content horizontal items", () => {
const gallery = render(
<Gallery {...defaultProps} scrollDirection="horizontal" horizontalItemSizing="fitContent" />
);
const list = gallery.getByTestId("gallery-test-list");
const firstItem = gallery.getByTestId("gallery-test-list-item-11");

expect(list.props.ItemSeparatorComponent).toBeUndefined();
expect(firstItem.props.style).toBeUndefined();
});

it("divides the available width between horizontal columns", () => {
const gallery = render(
<Gallery {...defaultProps} scrollDirection="horizontal" horizontalItemSizing="fillColumns" />
);

fireEvent(gallery.getByTestId("gallery-test"), "layout", {
nativeEvent: { layout: { width: 750, height: 100, x: 0, y: 0 } }
});

expect(gallery.getByTestId("gallery-test-list-item-11").props.style).toEqual({ width: 375 });
expect(gallery.getByTestId("gallery-test-list").props.ItemSeparatorComponent).toBeUndefined();
});

it("renders correctly with empty list and custom placeholder", () => {
const gallery = render(
<Gallery
Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/gallery-native/src/package.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="Gallery" version="2.2.2" xmlns="http://www.mendix.com/clientModule/1.0/">
<clientModule name="Gallery" version="2.3.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="Gallery.xml" />
</widgetFiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Big } from "big.js";

export type ScrollDirectionEnum = "vertical" | "horizontal";

export type HorizontalItemSizingEnum = "fullWidth" | "fitContent" | "fillColumns";

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

export interface FilterListType {
Expand All @@ -25,6 +27,7 @@ export interface GalleryProps<Style> {
datasource: ListValue;
content?: ListWidgetValue;
scrollDirection: ScrollDirectionEnum;
horizontalItemSizing: HorizontalItemSizingEnum;
tabletColumns: number;
phoneColumns: number;
pageSize: number;
Expand All @@ -51,6 +54,7 @@ export interface GalleryPreviewProps {
datasource: {} | { caption: string } | { type: string } | null;
content: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
scrollDirection: ScrollDirectionEnum;
horizontalItemSizing: HorizontalItemSizingEnum;
tabletColumns: number | null;
phoneColumns: number | null;
pageSize: number | null;
Expand Down
Loading