From a6ded6984f4ec37f94b22ac4f9157cfe5ee71b78 Mon Sep 17 00:00:00 2001 From: Saurabh Chavan Date: Wed, 12 Aug 2026 11:18:38 +0530 Subject: [PATCH 1/4] fix: remove reanimated library from FAB --- .../package.json | 3 +- .../src/FloatingActionButton.tsx | 83 ++++--- .../__tests__/FloatingActionButton.spec.tsx | 10 - .../FloatingActionButton.spec.tsx.snap | 217 ++++++++---------- pnpm-lock.yaml | 41 ---- 5 files changed, 141 insertions(+), 213 deletions(-) diff --git a/packages/pluggableWidgets/floating-action-button-native/package.json b/packages/pluggableWidgets/floating-action-button-native/package.json index 5db19bcef..e99ce1a07 100644 --- a/packages/pluggableWidgets/floating-action-button-native/package.json +++ b/packages/pluggableWidgets/floating-action-button-native/package.json @@ -20,8 +20,7 @@ }, "dependencies": { "@mendix/piw-native-utils-internal": "*", - "@mendix/piw-utils-internal": "*", - "react-native-reanimated": "^3.0.0" + "@mendix/piw-utils-internal": "*" }, "devDependencies": { "@mendix/pluggable-widgets-tools": "*" diff --git a/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx b/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx index 43fc419a4..572a47c59 100644 --- a/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx +++ b/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx @@ -1,9 +1,8 @@ import { flattenStyles } from "@mendix/piw-native-utils-internal"; import { executeAction } from "@mendix/piw-utils-internal"; import { Icon } from "mendix/components/native/Icon"; -import { Component, JSX } from "react"; -import { Pressable, StyleSheet, Text, View, ViewStyle } from "react-native"; -import Animated, { Easing, interpolate, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated"; +import { Component, JSX, useEffect, useRef } from "react"; +import { Animated, Easing, Pressable, StyleSheet, Text, View, ViewStyle } from "react-native"; import { FloatingActionButtonProps } from "../typings/FloatingActionButtonProps"; import { defaultFloatingActionButtonStyle, FloatingActionButtonStyle } from "./ui/styles"; @@ -27,19 +26,21 @@ interface AnimatedMainIconProps { function AnimatedMainIcon(props: AnimatedMainIconProps): JSX.Element { const { active, hasSecondaryButtons, style, icon, iconActive } = props; - const progress = useSharedValue(active && hasSecondaryButtons ? 1 : 0); - progress.value = withTiming(active && hasSecondaryButtons ? 1 : 0, { - duration: 200, - easing: Easing.out(Easing.ease) - }); + const progress = useRef(new Animated.Value(active && hasSecondaryButtons ? 1 : 0)).current; + + useEffect(() => { + Animated.timing(progress, { + toValue: active && hasSecondaryButtons ? 1 : 0, + duration: 200, + easing: Easing.out(Easing.ease), + useNativeDriver: true + }).start(); + }, [active, hasSecondaryButtons, progress]); - const animatedStyle = useAnimatedStyle(() => ({ - transform: [ - { - rotate: `${interpolate(progress.value, [0, 1], [0, -180])}deg` - } - ] - })); + const rotate = progress.interpolate({ + inputRange: [0, 1], + outputRange: ["0deg", "-180deg"] + }); const iconSource = icon?.value ? icon.value : defaultIconSource; const activeIconSource = iconActive?.value ? iconActive.value : defaultActiveIconSource; @@ -47,7 +48,7 @@ function AnimatedMainIcon(props: AnimatedMainIconProps): JSX.Element { return ( - + @@ -81,32 +82,31 @@ function SecondaryActionItem(props: SecondaryActionItemProps): JSX.Element { onPress } = props; - const progress = useSharedValue(active ? 1 : 0); - progress.value = withTiming(active ? 1 : 0, { - duration: 200, - easing: Easing.out(Easing.ease) - }); + const progress = useRef(new Animated.Value(active ? 1 : 0)).current; + + useEffect(() => { + Animated.timing(progress, { + toValue: active ? 1 : 0, + duration: 200, + easing: Easing.out(Easing.ease), + useNativeDriver: true + }).start(); + }, [active, progress]); const labelOnRight = horizontalPosition === "left"; const labelOnLeft = horizontalPosition === "right"; - const animatedStyle = useAnimatedStyle(() => { - const centerToCenterDistance = - (mainButtonSize + secondaryButtonSize) / 2 + SECONDARY_GAP + index * (secondaryButtonSize + SECONDARY_GAP); + const centerToCenterDistance = + (mainButtonSize + secondaryButtonSize) / 2 + SECONDARY_GAP + index * (secondaryButtonSize + SECONDARY_GAP); - return { - opacity: progress.value, - transform: [ - { - translateY: interpolate( - progress.value, - [0, 1], - [0, direction === "up" ? -centerToCenterDistance : centerToCenterDistance] - ) - }, - { scale: interpolate(progress.value, [0, 1], [0.8, 1]) } - ] - }; + const translateY = progress.interpolate({ + inputRange: [0, 1], + outputRange: [0, direction === "up" ? -centerToCenterDistance : centerToCenterDistance] + }); + + const scale = progress.interpolate({ + inputRange: [0, 1], + outputRange: [0.8, 1] }); const anchorStyle: ViewStyle = { @@ -119,7 +119,14 @@ function SecondaryActionItem(props: SecondaryActionItemProps): JSX.Element { return ( { - const Reanimated = jest.requireActual("react-native-reanimated/lib/module/mock"); - - if (Reanimated?.default && typeof Reanimated.default === "object") { - Reanimated.default.call = () => undefined; - } - - return Reanimated; -}); - describe("FloatingActionButton", () => { let defaultProps: FloatingActionButtonProps; const secondaryButtons = [ diff --git a/packages/pluggableWidgets/floating-action-button-native/src/__tests__/__snapshots__/FloatingActionButton.spec.tsx.snap b/packages/pluggableWidgets/floating-action-button-native/src/__tests__/__snapshots__/FloatingActionButton.spec.tsx.snap index 23cd874cf..534e3c657 100644 --- a/packages/pluggableWidgets/floating-action-button-native/src/__tests__/__snapshots__/FloatingActionButton.spec.tsx.snap +++ b/packages/pluggableWidgets/floating-action-button-native/src/__tests__/__snapshots__/FloatingActionButton.spec.tsx.snap @@ -23,30 +23,25 @@ exports[`FloatingActionButton renders correct props with secondary buttons 1`] = } > Date: Wed, 12 Aug 2026 11:46:15 +0530 Subject: [PATCH 2/4] fix: caption value bug fix for horizontal position center --- .../floating-action-button-native/src/FloatingActionButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx b/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx index 572a47c59..f6bb1baee 100644 --- a/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx +++ b/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx @@ -93,7 +93,7 @@ function SecondaryActionItem(props: SecondaryActionItemProps): JSX.Element { }).start(); }, [active, progress]); - const labelOnRight = horizontalPosition === "left"; + const labelOnRight = horizontalPosition === "left" || horizontalPosition === "center"; const labelOnLeft = horizontalPosition === "right"; const centerToCenterDistance = From 9cd1caf1b86da02aaea321e36f1da32a4daf2cdf Mon Sep 17 00:00:00 2001 From: Saurabh Chavan Date: Wed, 12 Aug 2026 15:37:44 +0530 Subject: [PATCH 3/4] fix: migrated class component to functional component --- .../src/FloatingActionButton.tsx | 299 ++++++++---------- .../src/ui/styles.tsx | 48 ++- 2 files changed, 174 insertions(+), 173 deletions(-) diff --git a/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx b/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx index f6bb1baee..b8d1df918 100644 --- a/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx +++ b/packages/pluggableWidgets/floating-action-button-native/src/FloatingActionButton.tsx @@ -1,15 +1,15 @@ import { flattenStyles } from "@mendix/piw-native-utils-internal"; import { executeAction } from "@mendix/piw-utils-internal"; import { Icon } from "mendix/components/native/Icon"; -import { Component, JSX, useEffect, useRef } from "react"; -import { Animated, Easing, Pressable, StyleSheet, Text, View, ViewStyle } from "react-native"; +import { JSX, useEffect, useRef, useState } from "react"; +import { Animated, Easing, Pressable, Text, View, ViewStyle } from "react-native"; -import { FloatingActionButtonProps } from "../typings/FloatingActionButtonProps"; -import { defaultFloatingActionButtonStyle, FloatingActionButtonStyle } from "./ui/styles"; - -interface State { - active: boolean; -} +import { + FloatingActionButtonProps, + HorizontalPositionEnum, + VerticalPositionEnum +} from "../typings/FloatingActionButtonProps"; +import { defaultFloatingActionButtonStyle, FloatingActionButtonStyle, styles } from "./ui/styles"; const defaultIconSource = { type: "glyph", iconClass: "glyphicon-plus" } as const; const defaultActiveIconSource = { type: "glyph", iconClass: "glyphicon-remove" } as const; @@ -23,6 +23,66 @@ interface AnimatedMainIconProps { iconActive: FloatingActionButtonProps["iconActive"]; } +interface SecondaryActionItemProps { + active: boolean; + index: number; + direction: "up" | "down"; + horizontalPosition: "left" | "right" | "center"; + name: string; + button: FloatingActionButtonProps["secondaryButtons"][number]; + style: FloatingActionButtonStyle; + mainButtonSize: number; + secondaryButtonSize: number; + onPress: () => void; +} + +function getVerticalOrientation(verticalPosition: VerticalPositionEnum): "up" | "down" { + switch (verticalPosition) { + case "bottom": + return "up"; + case "top": + default: + return "down"; + } +} + +function getPositionStyle( + verticalPosition: VerticalPositionEnum, + horizontalPosition: HorizontalPositionEnum +): ViewStyle { + const positionStyle: ViewStyle = { + position: "absolute", + left: 0, + right: 0, + zIndex: 999 + }; + + switch (verticalPosition) { + case "bottom": + positionStyle.bottom = 0; + break; + case "top": + default: + positionStyle.top = 0; + break; + } + + switch (horizontalPosition) { + case "left": + positionStyle.alignItems = "flex-start"; + break; + case "center": + positionStyle.alignItems = "center"; + break; + case "right": + default: + positionStyle.alignItems = "flex-end"; + break; + } + + return positionStyle; +} + function AnimatedMainIcon(props: AnimatedMainIconProps): JSX.Element { const { active, hasSecondaryButtons, style, icon, iconActive } = props; @@ -55,19 +115,6 @@ function AnimatedMainIcon(props: AnimatedMainIconProps): JSX.Element { ); } -interface SecondaryActionItemProps { - active: boolean; - index: number; - direction: "up" | "down"; - horizontalPosition: "left" | "right" | "center"; - name: string; - button: FloatingActionButtonProps["secondaryButtons"][number]; - style: FloatingActionButtonStyle; - mainButtonSize: number; - secondaryButtonSize: number; - onPress: () => void; -} - function SecondaryActionItem(props: SecondaryActionItemProps): JSX.Element { const { active, @@ -193,177 +240,85 @@ function SecondaryActionItem(props: SecondaryActionItemProps): JSX.Element { ); } -export class FloatingActionButton extends Component, State> { - readonly state: State = { - active: false - }; - - private readonly onPressHandler = this.onPress.bind(this); - - render(): JSX.Element { - const style = flattenStyles(defaultFloatingActionButtonStyle, this.props.style); - const buttonStyle = { ...style.button }; - delete buttonStyle.rippleColor; +export function FloatingActionButton(props: FloatingActionButtonProps): JSX.Element { + const [active, setActive] = useState(false); + const style = flattenStyles(defaultFloatingActionButtonStyle, props.style); + const buttonStyle = { ...style.button }; + delete buttonStyle.rippleColor; - const mainButtonSize = style.button.size ?? 54; - const secondaryButtonSize = style.secondaryButton.size ?? 40; - const horizontalPosition = this.props.horizontalPosition ?? "right"; - const hasSecondaryButtons = !!this.props.secondaryButtons?.length; + const mainButtonSize = style.button.size ?? 54; + const secondaryButtonSize = style.secondaryButton.size ?? 40; + const horizontalPosition = props.horizontalPosition ?? "right"; + const hasSecondaryButtons = !!props.secondaryButtons?.length; - return ( - - {this.renderButtons(style, mainButtonSize, secondaryButtonSize, horizontalPosition)} + const handlePress = (): void => { + if (props.secondaryButtons?.length) { + setActive(prev => !prev); + return; + } - [ - styles.mainButtonBase, - buttonStyle, - { - width: mainButtonSize, - height: mainButtonSize, - borderRadius: mainButtonSize / 2, - opacity: pressed ? 0.2 : 1 - } - ]} - > - - - - ); - } + executeAction(props.onClick); + }; - private renderButtons( + const renderButtons = ( style: FloatingActionButtonStyle, mainButtonSize: number, secondaryButtonSize: number, horizontalPosition: "left" | "right" | "center" - ): JSX.Element[] | undefined { - return this.props.secondaryButtons?.map((button, index) => ( + ): JSX.Element[] | undefined => { + return props.secondaryButtons?.map((button, index) => ( { - this.setState({ active: false }); + setActive(false); executeAction(button.onClick); }} /> )); - } - - private get verticalOrientation(): "up" | "down" { - switch (this.props.verticalPosition) { - case "bottom": - return "up"; - case "top": - return "down"; - default: - return "down"; - } - } - - private getPositionStyle(): ViewStyle { - const positionStyle: ViewStyle = { - position: "absolute", - left: 0, - right: 0, - zIndex: 999 - }; - - switch (this.props.verticalPosition) { - case "bottom": - positionStyle.bottom = 0; - break; - case "top": - default: - positionStyle.top = 0; - break; - } - - switch (this.props.horizontalPosition) { - case "left": - positionStyle.alignItems = "flex-start"; - break; - case "center": - positionStyle.alignItems = "center"; - break; - case "right": - default: - positionStyle.alignItems = "flex-end"; - break; - } - - return positionStyle; - } - - private onPress(): void { - if (this.props.secondaryButtons?.length) { - // eslint-disable-next-line react/no-access-state-in-setstate - this.setState({ active: !this.state.active }); - return; - } + }; - executeAction(this.props.onClick); - } + return ( + + {renderButtons(style, mainButtonSize, secondaryButtonSize, horizontalPosition)} + + [ + styles.mainButtonBase, + buttonStyle, + { + width: mainButtonSize, + height: mainButtonSize, + borderRadius: mainButtonSize / 2, + opacity: pressed ? 0.2 : 1 + } + ]} + > + + + + ); } - -const styles = StyleSheet.create({ - wrapper: { - justifyContent: "flex-end" - }, - mainButtonBase: { - alignItems: "center", - justifyContent: "center" - }, - secondaryAnchor: { - position: "absolute" - }, - secondaryRow: { - width: "100%", - flexDirection: "row", - alignItems: "center" - }, - rowAlignLeft: { - justifyContent: "flex-start" - }, - rowAlignRight: { - justifyContent: "flex-end" - }, - rowAlignCenter: { - justifyContent: "center" - }, - secondaryButtonBase: { - alignItems: "center", - justifyContent: "center", - flexShrink: 0 - }, - captionInlineContainer: { - flexShrink: 0 - }, - captionBeforeButton: { - marginRight: 8, - alignItems: "flex-end" - }, - captionAfterButton: { - marginLeft: 8, - alignItems: "flex-start" - }, - captionText: { - flexShrink: 0 - } -}); diff --git a/packages/pluggableWidgets/floating-action-button-native/src/ui/styles.tsx b/packages/pluggableWidgets/floating-action-button-native/src/ui/styles.tsx index a6802b783..e6c5dc764 100644 --- a/packages/pluggableWidgets/floating-action-button-native/src/ui/styles.tsx +++ b/packages/pluggableWidgets/floating-action-button-native/src/ui/styles.tsx @@ -1,5 +1,5 @@ import { Style } from "@mendix/piw-native-utils-internal"; -import { TextStyle, ViewStyle } from "react-native"; +import { TextStyle, ViewStyle, StyleSheet } from "react-native"; export interface FloatingActionButtonStyle extends Style { container: ViewStyle; @@ -80,3 +80,49 @@ export const defaultFloatingActionButtonStyle: FloatingActionButtonStyle = { marginHorizontal: 15 } }; + +export const styles = StyleSheet.create({ + wrapper: { + justifyContent: "flex-end" + }, + mainButtonBase: { + alignItems: "center", + justifyContent: "center" + }, + secondaryAnchor: { + position: "absolute" + }, + secondaryRow: { + width: "100%", + flexDirection: "row", + alignItems: "center" + }, + rowAlignLeft: { + justifyContent: "flex-start" + }, + rowAlignRight: { + justifyContent: "flex-end" + }, + rowAlignCenter: { + justifyContent: "center" + }, + secondaryButtonBase: { + alignItems: "center", + justifyContent: "center", + flexShrink: 0 + }, + captionInlineContainer: { + flexShrink: 0 + }, + captionBeforeButton: { + marginRight: 8, + alignItems: "flex-end" + }, + captionAfterButton: { + marginLeft: 8, + alignItems: "flex-start" + }, + captionText: { + flexShrink: 0 + } +}); From ab260594f45bdc488416db3e27a603eb23b167c7 Mon Sep 17 00:00:00 2001 From: Saurabh Chavan Date: Wed, 12 Aug 2026 15:48:12 +0530 Subject: [PATCH 4/4] fix: update version and changelog --- .../floating-action-button-native/CHANGELOG.md | 4 ++++ .../floating-action-button-native/package.json | 2 +- .../floating-action-button-native/src/package.xml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pluggableWidgets/floating-action-button-native/CHANGELOG.md b/packages/pluggableWidgets/floating-action-button-native/CHANGELOG.md index 72f4dfdcd..090c849d4 100644 --- a/packages/pluggableWidgets/floating-action-button-native/CHANGELOG.md +++ b/packages/pluggableWidgets/floating-action-button-native/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +- We have removed the 'react-native-reanimated' library and used Animated API of react native for animation purpose. +- We fixed an issue of secondary button's caption not displayed if its horizontal position is center. +- We refactored code and migrated to functional component. + ## [4.2.1] - 2026-6-10 ### Changed diff --git a/packages/pluggableWidgets/floating-action-button-native/package.json b/packages/pluggableWidgets/floating-action-button-native/package.json index e99ce1a07..03910be78 100644 --- a/packages/pluggableWidgets/floating-action-button-native/package.json +++ b/packages/pluggableWidgets/floating-action-button-native/package.json @@ -1,7 +1,7 @@ { "name": "floating-action-button-native", "widgetName": "FloatingActionButton", - "version": "4.2.1", + "version": "4.3.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/pluggableWidgets/floating-action-button-native/src/package.xml b/packages/pluggableWidgets/floating-action-button-native/src/package.xml index af33e8f2d..76a2254ae 100644 --- a/packages/pluggableWidgets/floating-action-button-native/src/package.xml +++ b/packages/pluggableWidgets/floating-action-button-native/src/package.xml @@ -1,6 +1,6 @@ - +