diff --git a/package.json b/package.json index 96845820e..b742014f4 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ }, "patchedDependencies": { "@mendix/pluggable-widgets-tools@11.8.0": "patches/@mendix+pluggable-widgets-tools+11.8.0.patch", - "@ptomasroos/react-native-multi-slider@1.0.0": "patches/@ptomasroos+react-native-multi-slider+1.0.0.patch", "react-native-action-button@2.8.5": "patches/react-native-action-button+2.8.5.patch", "react-native-gesture-handler@2.30.0": "patches/react-native-gesture-handler+2.30.0.patch", "react-native-slider@0.11.0": "patches/react-native-slider+0.11.0.patch", diff --git a/packages/pluggableWidgets/range-slider-native/package.json b/packages/pluggableWidgets/range-slider-native/package.json index 7295f23ca..dd396161e 100644 --- a/packages/pluggableWidgets/range-slider-native/package.json +++ b/packages/pluggableWidgets/range-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" + "@miblanchard/react-native-slider": "^2.6.0" }, "devDependencies": { - "@mendix/pluggable-widgets-tools": "*", - "@types/ptomasroos__react-native-multi-slider": "^0.0.1" + "@mendix/pluggable-widgets-tools": "*" } } diff --git a/packages/pluggableWidgets/range-slider-native/src/Marker.tsx b/packages/pluggableWidgets/range-slider-native/src/Marker.tsx deleted file mode 100644 index b2a02bfcf..000000000 --- a/packages/pluggableWidgets/range-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/range-slider-native/src/RangeSlider.tsx b/packages/pluggableWidgets/range-slider-native/src/RangeSlider.tsx index 368b53595..37a48d26b 100644 --- a/packages/pluggableWidgets/range-slider-native/src/RangeSlider.tsx +++ b/packages/pluggableWidgets/range-slider-native/src/RangeSlider.tsx @@ -1,18 +1,16 @@ import { available, flattenStyles, toNumber, unavailable } from "@mendix/piw-native-utils-internal"; -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 { Slider } from "@miblanchard/react-native-slider"; +import { ReactElement, useCallback, useRef } from "react"; +import { Text, View } from "react-native"; import { Big } from "big.js"; import { RangeSliderProps } from "../typings/RangeSliderProps"; -import { Marker } from "./Marker"; import { defaultRangeSliderStyle, RangeSliderStyle } from "./ui/Styles"; import { executeAction } from "@mendix/piw-utils-internal"; export type Props = RangeSliderProps; export function RangeSlider(props: Props): ReactElement { - const [width, setWidth] = useState(); const styles = flattenStyles(defaultRangeSliderStyle, props.style); const lastLowerValue = useRef(toNumber(props.lowerValueAttribute)); @@ -23,27 +21,13 @@ export function RangeSlider(props: Props): ReactElement { const validationMessages = validate(props); const validProps = validationMessages.length === 0; const editable = props.editable !== "never" && validProps; - const enabledOne = editable && lowerValue !== undefined && !props.lowerValueAttribute.readOnly; - const enabledTwo = editable && upperValue !== undefined && !props.upperValueAttribute.readOnly; + const enabledLower = editable && lowerValue !== undefined && !props.lowerValueAttribute.readOnly; + const enabledUpper = editable && upperValue !== undefined && !props.upperValueAttribute.readOnly; + const isEnabled = enabledLower || enabledUpper; - const customMarker = - (markerEnabled: boolean, testID: string) => - (markerProps: MarkerProps): JSX.Element => - ( - - ); - - const onLayout = useCallback((event: LayoutChangeEvent): void => { - setWidth(event.nativeEvent.layout.width); - }, []); - - const onSlide = useCallback( + const onValueChange = useCallback( (values: number[]): void => { - if (values[0] === null || values[1] === null) { + if (values[0] === null || values[0] === undefined || values[1] === null || values[1] === undefined) { return; } props.lowerValueAttribute.setValue(new Big(values[0])); @@ -52,11 +36,13 @@ export function RangeSlider(props: Props): ReactElement { [props.lowerValueAttribute, props.upperValueAttribute] ); - const onChange = useCallback( + const onSlidingComplete = useCallback( (values: number[]): void => { if ( values[0] === null || + values[0] === undefined || values[1] === null || + values[1] === undefined || (lastLowerValue.current === values[0] && lastUpperValue.current === values[1]) ) { return; @@ -73,24 +59,19 @@ export function RangeSlider(props: Props): ReactElement { ); return ( - - + {props.lowerValueAttribute.validation && ( {props.lowerValueAttribute.validation} diff --git a/packages/pluggableWidgets/range-slider-native/src/__tests__/RangeSlider.spec.tsx b/packages/pluggableWidgets/range-slider-native/src/__tests__/RangeSlider.spec.tsx index df54a1088..cd19d45e5 100644 --- a/packages/pluggableWidgets/range-slider-native/src/__tests__/RangeSlider.spec.tsx +++ b/packages/pluggableWidgets/range-slider-native/src/__tests__/RangeSlider.spec.tsx @@ -1,11 +1,23 @@ 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, RangeSlider } from "../RangeSlider"; +jest.mock("@miblanchard/react-native-slider", () => { + const { View } = require("react-native"); + return { + Slider: (props: any) => ( + props.onValueChange?.(values)} + onSlidingComplete={(values: number[]) => props.onSlidingComplete?.(values)} + /> + ) + }; +}); + describe("RangeSlider", () => { const noValue: DynamicValue = { status: ValueStatus.Unavailable, value: undefined }; let defaultProps: Props; @@ -32,7 +44,7 @@ describe("RangeSlider", () => { const component = render( ().isLoading().build()} /> ); - expect(component.queryByTestId(`${defaultProps.name}-validation-message`)).toBeNull(); + expect(component.queryByTestId(`${defaultProps.name}-validation-messages`)).toBeNull(); }); it("renders an error when no minimum value is provided", () => { @@ -102,29 +114,6 @@ describe("RangeSlider", () => { expect(component.queryByText("The upper value must be equal or less than the maximum value.")).not.toBeNull(); }); - it("renders with the width of the parent view", () => { - const component = render( - - ); - fireEvent(component.getByTestId("range-slider-test"), "layout", { nativeEvent: { layout: { width: 100 } } }); - expect(component.getByTestId("range-slider-test").findByProps({ sliderLength: 100 })).not.toBeNull(); - }); - it("renders a validation message", () => { const value = new EditableValueBuilder().withValidation("Invalid").build(); const component = render( @@ -134,71 +123,85 @@ describe("RangeSlider", () => { expect(component.getAllByText("Invalid")).toHaveLength(2); }); - it("changes the lower value when swiping", () => { - const onChangeAction = actionValue(); - const component = render(); + it("renders as disabled when editable is never", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + expect(slider.props.disabled).toBe(true); + }); - fireEvent(getHandle(component), "responderGrant", { touchHistory: { touchBank: [] } }); - fireEvent(getHandle(component), "responderMove", responderMove(50)); + it("renders as enabled when editable is default", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + expect(slider.props.disabled).toBe(false); + }); - expect(onChangeAction.execute).not.toHaveBeenCalled(); + it("passes correct min/max/step to the slider", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + expect(slider.props.minimumValue).toBe(0); + expect(slider.props.maximumValue).toBe(280); + expect(slider.props.step).toBe(1); + }); - fireEvent(getHandle(component), "responderRelease", {}); + it("passes range values as array", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + expect(slider.props.value).toEqual([70, 210]); + }); + + it("calls onValueChange when sliding", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + + fireEvent(slider, "onValueChange", [100, 210]); - expect(defaultProps.lowerValueAttribute.setValue).toHaveBeenCalledWith(new Big(120)); + expect(defaultProps.lowerValueAttribute.setValue).toHaveBeenCalledWith(new Big(100)); expect(defaultProps.upperValueAttribute.setValue).toHaveBeenCalledWith(new Big(210)); - expect(onChangeAction.execute).toHaveBeenCalledTimes(1); }); - it("changes the upper value when swiping", () => { + it("calls onChange action on sliding complete", () => { const onChangeAction = actionValue(); const component = render(); + const slider = component.getByTestId("mocked-slider"); - fireEvent(getHandle(component, 1), "responderGrant", { touchHistory: { touchBank: [] } }); - fireEvent(getHandle(component, 1), "responderMove", responderMove(-50)); - - expect(onChangeAction.execute).not.toHaveBeenCalled(); - - fireEvent(getHandle(component, 1), "responderRelease", {}); + fireEvent(slider, "onSlidingComplete", [100, 250]); - expect(defaultProps.lowerValueAttribute.setValue).toHaveBeenCalledWith(new Big(70)); - expect(defaultProps.upperValueAttribute.setValue).toHaveBeenCalledWith(new Big(160)); + expect(defaultProps.lowerValueAttribute.setValue).toHaveBeenCalledWith(new Big(100)); + expect(defaultProps.upperValueAttribute.setValue).toHaveBeenCalledWith(new Big(250)); expect(onChangeAction.execute).toHaveBeenCalledTimes(1); }); - it("does not change the value when non editable", () => { + it("does not call onChange when values haven't changed", () => { const onChangeAction = actionValue(); - const component = render(); + const component = render(); + const slider = component.getByTestId("mocked-slider"); - fireEvent(getHandle(component), "responderGrant", { touchHistory: { touchBank: [] } }); - fireEvent(getHandle(component), "responderMove", responderMove(50)); - fireEvent(getHandle(component), "responderRelease", {}); + fireEvent(slider, "onSlidingComplete", [70, 210]); expect(onChangeAction.execute).not.toHaveBeenCalled(); - expect(defaultProps.lowerValueAttribute.setValue).not.toHaveBeenCalled(); - expect(defaultProps.upperValueAttribute.setValue).not.toHaveBeenCalled(); }); -}); - -function getHandle(component: RenderAPI, index = 0): ReactTestInstance { - return component.UNSAFE_getAllByType(View).filter(instance => instance.props.onMoveShouldSetResponder)[index]; -} -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 - } - ] - } - }; -} + it("applies custom styles", () => { + const component = render( + + ); + expect(component.toJSON()).toMatchSnapshot("with custom styles"); + }); +}); diff --git a/packages/pluggableWidgets/range-slider-native/src/__tests__/__snapshots__/RangeSlider.spec.tsx.snap b/packages/pluggableWidgets/range-slider-native/src/__tests__/__snapshots__/RangeSlider.spec.tsx.snap index d73f70cd6..27d18dcfd 100644 --- a/packages/pluggableWidgets/range-slider-native/src/__tests__/__snapshots__/RangeSlider.spec.tsx.snap +++ b/packages/pluggableWidgets/range-slider-native/src/__tests__/__snapshots__/RangeSlider.spec.tsx.snap @@ -1,267 +1,99 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`RangeSlider applies custom styles: with custom styles 1`] = ` + + + +`; + exports[`RangeSlider renders 1`] = ` - - - - - - - - - - - - - - - - - - - - + /> `; diff --git a/packages/pluggableWidgets/range-slider-native/src/ui/Styles.ts b/packages/pluggableWidgets/range-slider-native/src/ui/Styles.ts index c77e1cf49..945f99f92 100644 --- a/packages/pluggableWidgets/range-slider-native/src/ui/Styles.ts +++ b/packages/pluggableWidgets/range-slider-native/src/ui/Styles.ts @@ -5,11 +5,13 @@ export interface RangeSliderStyle extends Style { container: ViewStyle; track: ViewStyle; trackDisabled: ViewStyle; - highlight: ViewStyle; - highlightDisabled: ViewStyle; - marker: ViewStyle; - markerActive: ViewStyle; - markerDisabled: ViewStyle; + minimumTrack: ViewStyle; + minimumTrackDisabled: ViewStyle; + maximumTrack: ViewStyle; + maximumTrackDisabled: ViewStyle; + thumb: ViewStyle; + thumbActive: ViewStyle; + thumbDisabled: ViewStyle; validationMessage: TextStyle; } @@ -22,34 +24,67 @@ const purpleLightest = "rgba(98,0,238, 0.1)"; export const defaultRangeSliderStyle: RangeSliderStyle = { container: {}, track: { - backgroundColor: Platform.select({ ios: blueLighter, android: purpleLighter }) + height: 4, + borderRadius: 2 }, trackDisabled: { + height: 4, + borderRadius: 2, ...Platform.select({ - ios: { - opacity: 0.4, - backgroundColor: blueLighter - }, - android: { - backgroundColor: "#EEE" - } + ios: { opacity: 0.4 }, + android: {} }) }, - highlight: { + minimumTrack: { backgroundColor: Platform.select({ ios: blue, android: purple }) }, - highlightDisabled: { + minimumTrackDisabled: { backgroundColor: Platform.select({ ios: blue, android: "#AAA" }) }, - marker: { + maximumTrack: { + backgroundColor: Platform.select({ ios: blueLighter, android: purpleLighter }) + }, + maximumTrackDisabled: { ...Platform.select({ + ios: { backgroundColor: blueLighter }, + android: { backgroundColor: "#EEE" } + }) + }, + thumb: { + ...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: { - borderColor: purple, + height: 12, + width: 12, + borderRadius: 12, backgroundColor: purple } }) }, - markerDisabled: { + thumbActive: { + ...Platform.select({ + android: { + height: 20, + width: 20, + borderRadius: 20, + borderWidth: 5, + borderColor: purpleLightest, + backgroundColor: purple + } + }) + }, + thumbDisabled: { ...Platform.select({ ios: { backgroundColor: "#FFF", @@ -62,14 +97,6 @@ export const defaultRangeSliderStyle: RangeSliderStyle = { } }) }, - markerActive: { - ...Platform.select({ - android: { - borderWidth: 5, - borderColor: purpleLightest - } - }) - }, validationMessage: { color: "#ed1c24" } diff --git a/packages/pluggableWidgets/slider-native/package.json b/packages/pluggableWidgets/slider-native/package.json index a881290b6..ee6efcbbb 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" + "@miblanchard/react-native-slider": "^2.6.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..5a50a5531 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 { Slider as RNSlider } from "@miblanchard/react-native-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,24 +19,12 @@ 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, - [] - ); - - const customMarker = - () => - (markerProps: MarkerProps): JSX.Element => - ; - const onLayout = useCallback((event: LayoutChangeEvent): void => { - setWidth(event.nativeEvent.layout.width); - }, []); + const decimalCount = useCallback((val: Option): number => val?.toString().split(".")?.[1]?.length || 0, []); - const onSlide = useCallback( + const onValueChange = useCallback( (values: number[]): void => { - if (values[0] === null) { + if (values[0] === null || values[0] === undefined) { return; } @@ -50,9 +35,9 @@ export function Slider(props: Props): ReactElement { [props.valueAttribute, props.stepSize, decimalCount] ); - const onChange = useCallback( + const onSlidingComplete = useCallback( (values: number[]): void => { - if (values[0] === null || lastValue.current === values[0]) { + if (values[0] === null || values[0] === undefined || lastValue.current === values[0]) { return; } @@ -67,22 +52,19 @@ 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..fbd18fad3 100644 --- a/packages/pluggableWidgets/slider-native/src/__tests__/Slider.spec.tsx +++ b/packages/pluggableWidgets/slider-native/src/__tests__/Slider.spec.tsx @@ -1,12 +1,24 @@ 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"; +jest.mock("@miblanchard/react-native-slider", () => { + const { View } = require("react-native"); + return { + Slider: (props: any) => ( + props.onValueChange?.(values)} + onSlidingComplete={(values: number[]) => props.onSlidingComplete?.(values)} + /> + ) + }; +}); + describe("Slider", () => { const noValue: DynamicValue = { status: ValueStatus.Unavailable, value: undefined }; let defaultProps: Props; @@ -93,29 +105,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 +112,77 @@ 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("renders as disabled when editable is never", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + expect(slider.props.disabled).toBe(true); }); - it("changes the value when swiping", () => { - const onChangeAction = actionValue(); - const component = render(); + it("renders as enabled when editable is default", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + expect(slider.props.disabled).toBe(false); + }); - fireEvent(getHandle(component), "responderGrant", { touchHistory: { touchBank: [] } }); - fireEvent(getHandle(component), "responderMove", responderMove(50)); + it("passes correct min/max/step to the slider", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + expect(slider.props.minimumValue).toBe(0); + expect(slider.props.maximumValue).toBe(280); + expect(slider.props.step).toBe(1); + }); - expect(onChangeAction.execute).not.toHaveBeenCalled(); + it("calls onValueChange when sliding", () => { + const component = render(); + const slider = component.getByTestId("mocked-slider"); + + fireEvent(slider, "onValueChange", [200]); + + expect(defaultProps.valueAttribute.setValue).toHaveBeenCalledWith(new Big(200)); + }); + + it("calls onChange action on sliding complete", () => { + const onChangeAction = actionValue(); + const component = render(); + const slider = component.getByTestId("mocked-slider"); - fireEvent(getHandle(component), "responderRelease", {}); + fireEvent(slider, "onSlidingComplete", [200]); - expect(defaultProps.valueAttribute.setValue).toHaveBeenCalledWith(new Big(190)); + expect(defaultProps.valueAttribute.setValue).toHaveBeenCalledWith(new Big(200)); expect(onChangeAction.execute).toHaveBeenCalledTimes(1); }); - it("does not change the value when non editable", () => { + it("does not call onChange when value hasn't changed", () => { const onChangeAction = actionValue(); - const component = render(); + const component = render(); + const slider = component.getByTestId("mocked-slider"); - fireEvent(getHandle(component), "responderGrant", { touchHistory: { touchBank: [] } }); - fireEvent(getHandle(component), "responderMove", responderMove(50)); - fireEvent(getHandle(component), "responderRelease", {}); + fireEvent(slider, "onSlidingComplete", [140]); expect(onChangeAction.execute).not.toHaveBeenCalled(); - expect(defaultProps.valueAttribute.setValue).not.toHaveBeenCalled(); }); -}); -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 - } - ] - } - }; -} + it("applies custom styles", () => { + const component = render( + + ); + expect(component.toJSON()).toMatchSnapshot("with custom styles"); + }); +}); 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..c5a6486ec 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 @@ -1,163 +1,89 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Slider applies custom styles: with custom styles 1`] = ` + + + +`; + exports[`Slider renders 1`] = ` - - - - - - - - - - - - + } + minimumValue={0} + onSlidingComplete={[Function]} + onValueChange={[Function]} + step={1} + testID="mocked-slider" + thumbStyle={ + { + "backgroundColor": "rgb(98,0,238)", + "borderRadius": 12, + "height": 12, + "width": 12, + } + } + trackStyle={ + { + "borderRadius": 2, + "height": 4, + } + } + value={140} + /> `; diff --git a/packages/pluggableWidgets/slider-native/src/ui/Styles.ts b/packages/pluggableWidgets/slider-native/src/ui/Styles.ts index 2edd936cd..cafbb3b07 100644 --- a/packages/pluggableWidgets/slider-native/src/ui/Styles.ts +++ b/packages/pluggableWidgets/slider-native/src/ui/Styles.ts @@ -5,11 +5,13 @@ export interface SliderStyle extends Style { container: ViewStyle; track: ViewStyle; trackDisabled: ViewStyle; - highlight: ViewStyle; - highlightDisabled: ViewStyle; - marker: ViewStyle; - markerActive: ViewStyle; - markerDisabled: ViewStyle; + minimumTrack: ViewStyle; + minimumTrackDisabled: ViewStyle; + maximumTrack: ViewStyle; + maximumTrackDisabled: ViewStyle; + thumb: ViewStyle; + thumbActive: ViewStyle; + thumbDisabled: ViewStyle; validationMessage: TextStyle; } @@ -22,34 +24,67 @@ const purpleLightest = "rgba(98,0,238, 0.1)"; export const defaultSliderStyle: SliderStyle = { container: {}, track: { - backgroundColor: Platform.select({ ios: blueLighter, android: purpleLighter }) + height: 4, + borderRadius: 2 }, trackDisabled: { + height: 4, + borderRadius: 2, ...Platform.select({ - ios: { - opacity: 0.4, - backgroundColor: blueLighter - }, - android: { - backgroundColor: "#EEE" - } + ios: { opacity: 0.4 }, + android: {} }) }, - highlight: { + minimumTrack: { backgroundColor: Platform.select({ ios: blue, android: purple }) }, - highlightDisabled: { + minimumTrackDisabled: { backgroundColor: Platform.select({ ios: blue, android: "#AAA" }) }, - marker: { + maximumTrack: { + backgroundColor: Platform.select({ ios: blueLighter, android: purpleLighter }) + }, + maximumTrackDisabled: { ...Platform.select({ + ios: { backgroundColor: blueLighter }, + android: { backgroundColor: "#EEE" } + }) + }, + thumb: { + ...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: { - borderColor: purple, + height: 12, + width: 12, + borderRadius: 12, backgroundColor: purple } }) }, - markerDisabled: { + thumbActive: { + ...Platform.select({ + android: { + height: 20, + width: 20, + borderRadius: 20, + borderWidth: 5, + borderColor: purpleLightest, + backgroundColor: purple + } + }) + }, + thumbDisabled: { ...Platform.select({ ios: { backgroundColor: "#FFF", @@ -62,14 +97,6 @@ export const defaultSliderStyle: SliderStyle = { } }) }, - markerActive: { - ...Platform.select({ - android: { - borderWidth: 5, - borderColor: purpleLightest - } - }) - }, validationMessage: { color: "#ed1c24" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 519a53900..600ced6c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,9 +28,6 @@ patchedDependencies: '@mendix/pluggable-widgets-tools@11.8.0': hash: 00a33321fd423a98cae205a3f6952aa8c0c0e0aad58aa6568d1b39346a5f04a0 path: patches/@mendix+pluggable-widgets-tools+11.8.0.patch - '@ptomasroos/react-native-multi-slider@1.0.0': - hash: b5e11465e4305f5284e90a78fc4575401f791921f34dbbafb9831f19ecae94da - path: patches/@ptomasroos+react-native-multi-slider+1.0.0.patch react-native-action-button@2.8.5: hash: 593bb64b27425a7f3805ad9567928d1369fd4cf939ab5d3eb43411a759565c48 path: patches/react-native-action-button+2.8.5.patch @@ -775,19 +772,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.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) - prop-types: - specifier: ^15.7.2 - version: 15.8.1 + '@miblanchard/react-native-slider': + specifier: ^2.6.0 + version: 2.6.0(react-native@0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) devDependencies: '@mendix/pluggable-widgets-tools': specifier: 11.8.0 version: 11.8.0(patch_hash=00a33321fd423a98cae205a3f6952aa8c0c0e0aad58aa6568d1b39346a5f04a0)(@jest/transform@29.7.0)(@jest/types@30.0.1)(@types/babel__core@7.20.5)(@types/node@25.5.0)(encoding@0.1.13)(jest-util@30.0.2)(picomatch@4.0.3)(react-dom@19.2.4(react@19.2.4))(react-native@0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(tslib@2.8.1) - '@types/ptomasroos__react-native-multi-slider': - specifier: ^0.0.1 - version: 0.0.1(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(react@19.2.4) packages/pluggableWidgets/rating-native: dependencies: @@ -873,19 +864,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.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) - prop-types: - specifier: ^15.7.2 - version: 15.8.1 + '@miblanchard/react-native-slider': + specifier: ^2.6.0 + version: 2.6.0(react-native@0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) devDependencies: '@mendix/pluggable-widgets-tools': specifier: 11.8.0 version: 11.8.0(patch_hash=00a33321fd423a98cae205a3f6952aa8c0c0e0aad58aa6568d1b39346a5f04a0)(@jest/transform@29.7.0)(@jest/types@30.0.1)(@types/babel__core@7.20.5)(@types/node@25.5.0)(encoding@0.1.13)(jest-util@30.0.2)(picomatch@4.0.3)(react-dom@19.2.4(react@19.2.4))(react-native@0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(tslib@2.8.1) - '@types/ptomasroos__react-native-multi-slider': - specifier: ^0.0.1 - version: 0.0.1(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(react@19.2.4) packages/pluggableWidgets/switch-native: dependencies: @@ -2233,6 +2218,12 @@ packages: engines: {node: '>=20'} hasBin: true + '@miblanchard/react-native-slider@2.6.0': + resolution: {integrity: sha512-o7hk/f/8vkqh6QNR5L52m+ws846fQeD/qNCC9CCSRdBqjq66KiCgbxzlhRzKM/gbtxcvMYMIEEJ1yes5cr6I3A==} + peerDependencies: + react: 19.2.4 + react-native: 0.83.3 + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} @@ -2348,13 +2339,6 @@ packages: '@prettier/plugin-xml@2.2.0': resolution: {integrity: sha512-UWRmygBsyj4bVXvDiqSccwT1kmsorcwQwaIy30yVh8T+Gspx4OlC0shX1y+ZuwXZvgnafmpRYKks0bAu9urJew==} - '@ptomasroos/react-native-multi-slider@1.0.0': - resolution: {integrity: sha512-NpX22rQLArg9widwMzGf7XsInTDf6mfY/D1XaDVjglNkVphj3NSN37+nF6MofArCxC++1P+jHv0SGWbmJQwy4g==} - peerDependencies: - prop-types: '*' - react: 19.2.4 - react-native: 0.83.3 - '@react-native-async-storage/async-storage@2.2.0': resolution: {integrity: sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==} peerDependencies: @@ -2886,9 +2870,6 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/ptomasroos__react-native-multi-slider@0.0.1': - resolution: {integrity: sha512-Jn+VZEYHAt8V797mMSABg+qCiU+pBklHxxI5bq294cKFSNa6eN5LIojyvaZBHCns0kY5viiTquCKthRwI+zybQ==} - '@types/querystringify@2.0.2': resolution: {integrity: sha512-7d6OQK6pJ//zE32XLK3vI6GHYhBDcYooaRco9cKFGNu59GVatL5+u7rkiAViq44DxDTd/7QQNBWSDHfJGBz/Pw==} @@ -9806,6 +9787,11 @@ snapshots: - tslib - utf-8-validate + '@miblanchard/react-native-slider@2.6.0(react-native@0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)': + dependencies: + react: 19.2.4 + react-native: 0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4) + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -9895,12 +9881,6 @@ snapshots: '@xml-tools/parser': 1.0.11 prettier: 2.8.8 - '@ptomasroos/react-native-multi-slider@1.0.0(patch_hash=b5e11465e4305f5284e90a78fc4575401f791921f34dbbafb9831f19ecae94da)(prop-types@15.8.1)(react-native@0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)': - dependencies: - prop-types: 15.8.1 - react: 19.2.4 - react-native: 0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4) - '@react-native-async-storage/async-storage@2.2.0(react-native@0.83.3(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4))': dependencies: merge-options: 3.0.4 @@ -10663,19 +10643,6 @@ snapshots: '@types/parse-json@4.0.2': {} - '@types/ptomasroos__react-native-multi-slider@0.0.1(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(react@19.2.4)': - dependencies: - '@types/react': 19.2.14 - '@types/react-native': 0.73.0(@babel/core@7.28.0)(@react-native-community/cli@14.1.0(typescript@5.9.3))(@types/react@19.2.14)(react@19.2.4) - transitivePeerDependencies: - - '@babel/core' - - '@react-native-community/cli' - - '@react-native/metro-config' - - bufferutil - - react - - supports-color - - utf-8-validate - '@types/querystringify@2.0.2': {} '@types/react-dom@19.2.3(@types/react@19.2.14)':