diff --git a/packages/pluggableWidgets/slider-native/package.json b/packages/pluggableWidgets/slider-native/package.json index 88e05de9c..600fc1440 100644 --- a/packages/pluggableWidgets/slider-native/package.json +++ b/packages/pluggableWidgets/slider-native/package.json @@ -21,11 +21,9 @@ "dependencies": { "@mendix/piw-native-utils-internal": "*", "@mendix/piw-utils-internal": "*", - "@ptomasroos/react-native-multi-slider": "^1.0.0", - "prop-types": "^15.7.2" + "@react-native-community/slider": "^5.2.0" }, "devDependencies": { - "@mendix/pluggable-widgets-tools": "*", - "@types/ptomasroos__react-native-multi-slider": "^0.0.1" + "@mendix/pluggable-widgets-tools": "*" } } diff --git a/packages/pluggableWidgets/slider-native/src/Marker.tsx b/packages/pluggableWidgets/slider-native/src/Marker.tsx deleted file mode 100644 index b2a02bfcf..000000000 --- a/packages/pluggableWidgets/slider-native/src/Marker.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { MarkerProps } from "@ptomasroos/react-native-multi-slider"; -import { ReactElement } from "react"; -import { Platform, StyleSheet, TouchableHighlight, View } from "react-native"; - -export function Marker(props: MarkerProps & { testID: string }): ReactElement { - return ( - - - - ); -} - -const styles = StyleSheet.create({ - markerStyle: { - ...Platform.select({ - ios: { - height: 30, - width: 30, - borderRadius: 30, - borderWidth: 1, - borderColor: "#DDDDDD", - backgroundColor: "#FFFFFF", - shadowColor: "#000000", - shadowOffset: { - width: 0, - height: 3 - }, - shadowRadius: 1, - shadowOpacity: 0.2 - }, - android: { - height: 12, - width: 12, - borderRadius: 12, - backgroundColor: "#0D8675" - } - }) - }, - pressedMarkerStyle: { - ...Platform.select({ - ios: {}, - android: { - height: 20, - width: 20, - borderRadius: 20 - } - }) - }, - disabled: { - backgroundColor: "#d3d3d3" - } -}); diff --git a/packages/pluggableWidgets/slider-native/src/Slider.tsx b/packages/pluggableWidgets/slider-native/src/Slider.tsx index 685b71d53..4aaa09c86 100644 --- a/packages/pluggableWidgets/slider-native/src/Slider.tsx +++ b/packages/pluggableWidgets/slider-native/src/Slider.tsx @@ -1,20 +1,17 @@ import { available, flattenStyles, toNumber, unavailable } from "@mendix/piw-native-utils-internal"; import { executeAction } from "@mendix/piw-utils-internal"; -import { ValueStatus, Option } from "mendix"; -import MultiSlider, { MarkerProps } from "@ptomasroos/react-native-multi-slider"; -import { ReactElement, useCallback, useRef, useState, JSX } from "react"; -import { LayoutChangeEvent, Text, View } from "react-native"; +import { ValueStatus } from "mendix"; +import RNSlider from "@react-native-community/slider"; +import { ReactElement, useCallback, useRef } from "react"; +import { Text, View } from "react-native"; import { Big } from "big.js"; import { SliderProps } from "../typings/SliderProps"; -import { Marker } from "./Marker"; import { defaultSliderStyle, SliderStyle } from "./ui/Styles"; export type Props = SliderProps; export function Slider(props: Props): ReactElement { - const [width, setWidth] = useState(); - const lastValue = useRef(toNumber(props.valueAttribute)); const value = toNumber(props.valueAttribute); @@ -22,43 +19,34 @@ export function Slider(props: Props): ReactElement { const validProps = validationMessages.length === 0; const editable = props.editable !== "never" && !props.valueAttribute.readOnly && validProps; const styles = flattenStyles(defaultSliderStyle, props.style); - // We have to fix the decimal count ourselves because of an unresolved bug in the library: https://github.com/ptomasroos/react-native-multi-slider/issues/211 + const decimalCount = useCallback( - (value: Option): number => value?.toString().split(".")?.[1]?.length || 0, + (val: Big | undefined): number => val?.toString().split(".")?.[1]?.length || 0, [] ); - const customMarker = - () => - (markerProps: MarkerProps): JSX.Element => - ; - - const onLayout = useCallback((event: LayoutChangeEvent): void => { - setWidth(event.nativeEvent.layout.width); - }, []); - const onSlide = useCallback( - (values: number[]): void => { - if (values[0] === null) { + (newValue: number): void => { + if (newValue === null) { return; } if (props.stepSize.status === ValueStatus.Available) { - props.valueAttribute.setValue(new Big(values[0].toFixed(decimalCount(props.stepSize.value)))); + props.valueAttribute.setValue(new Big(newValue.toFixed(decimalCount(props.stepSize.value)))); } }, [props.valueAttribute, props.stepSize, decimalCount] ); const onChange = useCallback( - (values: number[]): void => { - if (values[0] === null || lastValue.current === values[0]) { + (newValue: number): void => { + if (newValue === null || lastValue.current === newValue) { return; } - lastValue.current = values[0]; + lastValue.current = newValue; if (props.stepSize.status === ValueStatus.Available) { - props.valueAttribute.setValue(new Big(values[0].toFixed(decimalCount(props.stepSize.value)))); + props.valueAttribute.setValue(new Big(newValue.toFixed(decimalCount(props.stepSize.value)))); } executeAction(props.onChange); @@ -67,22 +55,29 @@ export function Slider(props: Props): ReactElement { ); return ( - - + {!validProps && {validationMessages.join("\n")}} {props.valueAttribute.validation && ( diff --git a/packages/pluggableWidgets/slider-native/src/__tests__/Slider.spec.tsx b/packages/pluggableWidgets/slider-native/src/__tests__/Slider.spec.tsx index bf74fc3e6..ab0b93472 100644 --- a/packages/pluggableWidgets/slider-native/src/__tests__/Slider.spec.tsx +++ b/packages/pluggableWidgets/slider-native/src/__tests__/Slider.spec.tsx @@ -1,8 +1,6 @@ import { actionValue, dynamicValue, EditableValueBuilder } from "@mendix/piw-utils-internal"; import { Big } from "big.js"; -import { View } from "react-native"; -import { fireEvent, render, RenderAPI } from "@testing-library/react-native"; -import { ReactTestInstance } from "react-test-renderer"; +import { fireEvent, render } from "@testing-library/react-native"; import { ValueStatus, DynamicValue } from "mendix"; import { Props, Slider } from "../Slider"; @@ -93,29 +91,6 @@ describe("Slider", () => { expect(component.queryByText("The current value can not be greater than the maximum value.")).not.toBeNull(); }); - it("renders with the width of the parent view", () => { - const component = render( - - ); - fireEvent(component.getByTestId("slider-test"), "layout", { nativeEvent: { layout: { width: 100 } } }); - expect(component.getByTestId("slider-test").findByProps({ sliderLength: 100 })).not.toBeNull(); - }); - it("renders a validation message", () => { const value = new EditableValueBuilder().withValidation("Invalid").build(); const component = render(); @@ -123,61 +98,31 @@ describe("Slider", () => { expect(component.queryByText("Invalid")).not.toBeNull(); }); - it.skip("handles an invalid step size", () => { - const component = render(); - expect(component.getByTestId("slider-test").findByProps({ step: 1 })).not.toBeNull(); - }); - - it("changes the value when swiping", () => { + it("changes the value when sliding completes", () => { const onChangeAction = actionValue(); const component = render(); - fireEvent(getHandle(component), "responderGrant", { touchHistory: { touchBank: [] } }); - fireEvent(getHandle(component), "responderMove", responderMove(50)); - - expect(onChangeAction.execute).not.toHaveBeenCalled(); - - fireEvent(getHandle(component), "responderRelease", {}); + const slider = component.getByTestId("slider-test$slider"); + fireEvent(slider, "onSlidingComplete", 190); expect(defaultProps.valueAttribute.setValue).toHaveBeenCalledWith(new Big(190)); expect(onChangeAction.execute).toHaveBeenCalledTimes(1); }); + it("calls onValueChange while sliding", () => { + const component = render(); + + const slider = component.getByTestId("slider-test$slider"); + fireEvent(slider, "onValueChange", 150); + + expect(defaultProps.valueAttribute.setValue).toHaveBeenCalledWith(new Big(150)); + }); + it("does not change the value when non editable", () => { const onChangeAction = actionValue(); const component = render(); - fireEvent(getHandle(component), "responderGrant", { touchHistory: { touchBank: [] } }); - fireEvent(getHandle(component), "responderMove", responderMove(50)); - fireEvent(getHandle(component), "responderRelease", {}); - - expect(onChangeAction.execute).not.toHaveBeenCalled(); - expect(defaultProps.valueAttribute.setValue).not.toHaveBeenCalled(); + const slider = component.getByTestId("slider-test$slider"); + expect(slider.props.disabled).toBe(true); }); }); - -function getHandle(component: RenderAPI): ReactTestInstance { - return component - .getByTestId("slider-test") - .findAllByType(View) - .filter(instance => instance.props.onMoveShouldSetResponder)[0]; -} - -function responderMove(dx: number): any { - return { - touchHistory: { - numberActiveTouches: 1, - indexOfSingleActiveTouch: 0, - touchBank: [ - { - touchActive: true, - currentTimeStamp: Date.now(), - currentPageX: dx, - currentPageY: 0, - previousPageX: 0, - previousPageY: 0 - } - ] - } - }; -} diff --git a/packages/pluggableWidgets/slider-native/src/__tests__/__snapshots__/Slider.spec.tsx.snap b/packages/pluggableWidgets/slider-native/src/__tests__/__snapshots__/Slider.spec.tsx.snap index 927cf2876..b5dc18283 100644 --- a/packages/pluggableWidgets/slider-native/src/__tests__/__snapshots__/Slider.spec.tsx.snap +++ b/packages/pluggableWidgets/slider-native/src/__tests__/__snapshots__/Slider.spec.tsx.snap @@ -2,162 +2,63 @@ exports[`Slider renders 1`] = ` - - - - - - - - - - - + tapToSeek={false} + testID="slider-test$slider" + thumbTintColor="rgb(98,0,238)" + upperLimit={9007199254740991} + value={140} + /> `; diff --git a/packages/pluggableWidgets/slider-native/src/ui/Styles.ts b/packages/pluggableWidgets/slider-native/src/ui/Styles.ts index 2edd936cd..bfc5eb88c 100644 --- a/packages/pluggableWidgets/slider-native/src/ui/Styles.ts +++ b/packages/pluggableWidgets/slider-native/src/ui/Styles.ts @@ -1,6 +1,15 @@ import { Style } from "@mendix/piw-native-utils-internal"; import { Platform, TextStyle, ViewStyle } from "react-native"; +interface SliderColors { + minimumTrackTintColor?: string; + maximumTrackTintColor?: string; + thumbTintColor?: string; + minimumTrackTintColorDisabled?: string; + maximumTrackTintColorDisabled?: string; + thumbTintColorDisabled?: string; +} + export interface SliderStyle extends Style { container: ViewStyle; track: ViewStyle; @@ -11,6 +20,8 @@ export interface SliderStyle extends Style { markerActive: ViewStyle; markerDisabled: ViewStyle; validationMessage: TextStyle; + /** Color configuration for the native slider component. */ + sliderColors: SliderColors; } const blue = "rgb(0,122,255)"; @@ -72,5 +83,13 @@ export const defaultSliderStyle: SliderStyle = { }, validationMessage: { color: "#ed1c24" + }, + sliderColors: { + minimumTrackTintColor: Platform.select({ ios: blue, android: purple }), + maximumTrackTintColor: Platform.select({ ios: blueLighter, android: purpleLighter }), + thumbTintColor: Platform.select({ android: purple }), + minimumTrackTintColorDisabled: Platform.select({ ios: blue, android: "#AAA" }), + maximumTrackTintColorDisabled: Platform.select({ ios: blueLighter, android: "#EEE" }), + thumbTintColorDisabled: Platform.select({ android: "#AAA" }) } }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e4afb67d..dd049e45f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -858,19 +858,13 @@ importers: '@mendix/piw-utils-internal': specifier: '*' version: link:../../tools/piw-utils-internal - '@ptomasroos/react-native-multi-slider': - specifier: ^1.0.0 - version: 1.0.0(patch_hash=b5e11465e4305f5284e90a78fc4575401f791921f34dbbafb9831f19ecae94da)(prop-types@15.8.1)(react-native@0.78.2(@babel/core@7.28.0)(@babel/preset-env@7.28.0(@babel/core@7.28.0))(@react-native-community/cli@14.1.0(typescript@5.8.3))(@types/react@19.0.14)(react@19.0.0))(react@19.0.0) - prop-types: - specifier: ^15.7.2 - version: 15.8.1 + '@react-native-community/slider': + specifier: ^5.2.0 + version: 5.2.0 devDependencies: '@mendix/pluggable-widgets-tools': specifier: 10.21.1 version: 10.21.1(patch_hash=d453a814eaf17b7f8d4239b31793af4ff954c6c420ad02ae92cce4efcb6c449f)(@jest/transform@29.7.0)(@jest/types@30.0.1)(@types/babel__core@7.20.5)(@types/node@20.19.9)(encoding@0.1.13)(jest-util@30.0.2)(picomatch@4.0.3)(react-dom@19.0.0(react@19.0.0))(react-native@0.78.2(@babel/core@7.28.0)(@babel/preset-env@7.28.0(@babel/core@7.28.0))(@react-native-community/cli@14.1.0(typescript@5.8.3))(@types/react@19.0.14)(react@19.0.0))(react@19.0.0)(tslib@2.8.1) - '@types/ptomasroos__react-native-multi-slider': - specifier: ^0.0.1 - version: 0.0.1(@babel/core@7.28.0)(@babel/preset-env@7.28.0(@babel/core@7.28.0))(@react-native-community/cli@14.1.0(typescript@5.8.3))(react@19.0.0) packages/pluggableWidgets/switch-native: dependencies: @@ -2200,6 +2194,9 @@ packages: peerDependencies: react-native: 0.78.2 + '@react-native-community/slider@5.2.0': + resolution: {integrity: sha512-484sH8aWEaSjxaZ7HT3YZ8CKDcNes2synko1vdEz5DFEdvKAduxKJTj22L/qBMD7rtIkfbX69DMzWDAGbOAV6w==} + '@react-native-firebase/app@20.1.0': resolution: {integrity: sha512-FCcTtmfz/Bk2laOEKOiUrQUkAnzerkRml7d3kZzJSxaBWLFxpWJQnnXqGZmD8hNWio2QEauB8llUD71KiDk+sw==} peerDependencies: @@ -9655,6 +9652,8 @@ snapshots: dependencies: react-native: 0.78.2(@babel/core@7.28.0)(@babel/preset-env@7.28.0(@babel/core@7.28.0))(@react-native-community/cli@14.1.0(typescript@5.8.3))(@types/react@19.0.14)(react@19.0.0) + '@react-native-community/slider@5.2.0': {} + '@react-native-firebase/app@20.1.0(react-native@0.78.2(@babel/core@7.28.0)(@babel/preset-env@7.28.0(@babel/core@7.28.0))(@react-native-community/cli@14.1.0(typescript@5.8.3))(@types/react@19.0.14)(react@19.0.0))(react@19.0.0)': dependencies: opencollective-postinstall: 2.0.3