Skip to content
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

feat(carousel-native): association #95

Open
wants to merge 1 commit 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
@@ -1,4 +1,5 @@
import { RowLayoutProps, StructurePreviewProps, topBar } from "@mendix/piw-utils-internal";
import { hidePropertiesIn, Properties } from "@mendix/pluggable-widgets-tools";

import paginationSVG from "./assets/pagination.svg";

Expand Down Expand Up @@ -37,3 +38,11 @@ export function getPreview(values: CarouselPreviewProps, isDarkMode: boolean): S

return topBar("Carousel", content, isDarkMode);
}

export function getProperties(values: CarouselPreviewProps, defaultProperties: Properties): Properties {
if (!values.activeSelection) {
hidePropertiesIn(defaultProperties, values, ["onChangeAction", "animateExpression"]);
}

return defaultProperties;
}
68 changes: 61 additions & 7 deletions packages/pluggableWidgets/carousel-native/src/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,71 @@ export const Carousel = (props: CarouselProps<CarouselStyle>): ReactElement => {

const [activeSlide, setActiveSlide] = useState(0);

const [firstItem, setFirstItem] = useState(0);

const [loading, setLoading] = useState(true);

useEffect(() => {
if (props.contentSource?.status === ValueStatus.Available) {
if (props.contentSource?.status === ValueStatus.Available && loading) {
// Set initial index of the first item to show the associated active selection.
const index =
(props.activeSelection?.value
? props.contentSource?.items?.findIndex(i => i.id === props.activeSelection?.value?.id)
: 0) ?? 0;
setFirstItem(index);
setActiveSlide(index);
setLoading(false);
}
}, [props.contentSource]);
}, [loading, props.activeSelection, props.contentSource]);

const onSnap = useCallback((index: number) => {
setActiveSlide(index);
}, []);
useEffect(() => {
if (
carouselRef &&
props.contentSource.status === "available" &&
props.activeSelection?.status === "available"
) {
let index = props.contentSource.items?.findIndex(i => i.id === props.activeSelection?.value?.id) ?? 0;
// Removed item that is active selection can not be found
index = index >= 0 ? index : 0;
// Should check carouselRef.currentIndex though this is not fast enough for update.
if (index !== activeSlide) {
// Update carousel when associated item is changed
setActiveSlide(index);
const animate = props.animateExpression?.value ?? true;
// Async snap to index, use case add item is added before current selected
setTimeout(() => {
(carouselRef as NativeCarousel<ObjectItem>).snapToItem(index, animate);
}, 1);
}
}
}, [activeSlide, carouselRef, props.activeSelection, props.animateExpression, props.contentSource]);

useEffect(() => {
if (props.contentSource.status === "available" && props.activeSelection?.status === "available") {
// Check if selected item is still available, reset to index 0 or null
let item = props.contentSource.items?.find(i => i.id === props.activeSelection?.value?.id);
if (item == null) {
item = props.contentSource.items?.[0];
}
if (props.activeSelection.value?.id !== item?.id) {
// Set association when empty to first slide
props.activeSelection.setValue(item);
}
}
}, [props.activeSelection, props.contentSource]);

const onSnap = useCallback(
(index: number) => {
setActiveSlide(index);
if (props.activeSelection) {
const item = props.contentSource?.items?.[index];
if (item?.id !== props.activeSelection.value?.id) {
props.activeSelection.setValue(item);
}
}
},
[props.activeSelection, props.contentSource]
);

const renderItem = useCallback(({ item, index }: { item: ObjectItem; index: number }) => {
const viewStyle = layoutSpecificStyle.slideItem;
Expand Down Expand Up @@ -97,7 +151,7 @@ export const Carousel = (props: CarouselProps<CarouselStyle>): ReactElement => {
);
}, [activeSlide, carouselRef, props.contentSource, props.showPagination]);

const onLayout = (event: LayoutChangeEvent) => {
const onLayout = (event: LayoutChangeEvent): void => {
let viewHeight = event.nativeEvent.layout.height;
const viewWidth = event.nativeEvent.layout.width;

Expand Down Expand Up @@ -149,7 +203,7 @@ export const Carousel = (props: CarouselProps<CarouselStyle>): ReactElement => {
testID={`${props.name}$carousel`}
activeSlideAlignment={props.activeSlideAlignment}
layout="default"
firstItem={0}
firstItem={firstItem}
useScrollView
enableSnap
data={props.contentSource.items}
Expand Down
20 changes: 20 additions & 0 deletions packages/pluggableWidgets/carousel-native/src/Carousel.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@
<caption>Content</caption>
<description/>
</property>
<property key="activeSelection" type="association" selectableObjects="contentSource" onChange="onChangeAction" required="false">
<caption>Active selection</caption>
<description/>
<associationTypes>
<associationType name="Reference"/>
</associationTypes>
</property>
</propertyGroup>
<propertyGroup caption="Effects">
<property key="animateExpression" type="expression" required="false">
<caption>Animate changed</caption>
<description>Animate when 'Active selection' association is changed, animation on user swiping will always be on.</description>
<returnType type="Boolean" />
</property>
</propertyGroup>
<propertyGroup caption="Display">
<property key="layout" type="enumeration" defaultValue="card">
Expand All @@ -41,6 +55,12 @@
</enumerationValues>
</property>
</propertyGroup>
<propertyGroup caption="Events">
<property key="onChangeAction" type="action" required="false">
<caption>On change</caption>
<description>When active selection association is changed.</description>
</property>
</propertyGroup>
<!-- Library has a bug with loops-->
<!-- https://github.com/archriss/react-native-snap-carousel/issues/653 - -->
<!-- https://github.com/archriss/react-native-snap-carousel/issues/608-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @author Mendix Widgets Framework Team
*/
import { ComponentType, CSSProperties, ReactNode } from "react";
import { ListValue, ListWidgetValue } from "mendix";
import { DynamicValue, ListValue, ListWidgetValue, ReferenceValue } from "mendix";

export type LayoutEnum = "card" | "fullWidth";

Expand All @@ -15,6 +15,8 @@ export interface CarouselProps<Style> {
style: Style[];
contentSource: ListValue;
content: ListWidgetValue;
activeSelection?: ReferenceValue;
animateExpression?: DynamicValue<boolean>;
layout: LayoutEnum;
showPagination: boolean;
activeSlideAlignment: ActiveSlideAlignmentEnum;
Expand All @@ -31,7 +33,10 @@ export interface CarouselPreviewProps {
readOnly: boolean;
contentSource: {} | { caption: string } | { type: string } | null;
content: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> };
activeSelection: string;
animateExpression: string;
layout: LayoutEnum;
showPagination: boolean;
activeSlideAlignment: ActiveSlideAlignmentEnum;
onChangeAction: {} | null;
}
Loading