From 9017a9968de366b6764c828a74a583c9a184c081 Mon Sep 17 00:00:00 2001 From: matcane Date: Sun, 27 Sep 2026 16:24:15 +0200 Subject: [PATCH 1/7] feat(slider): add labels props --- src/ImageComparisonSlider.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ImageComparisonSlider.tsx b/src/ImageComparisonSlider.tsx index 0be5890..b986bbf 100644 --- a/src/ImageComparisonSlider.tsx +++ b/src/ImageComparisonSlider.tsx @@ -44,6 +44,12 @@ export interface ImageComparisonSliderProps { /** after */ afterImageProps?: PassThroughImageProps; + + /** Label on the before image */ + beforeLabel?: string; + + /** LAbel on the after image */ + afterLabel?: string; } /** From aaad13e0d1f7e85e7c628aaa4e433e44ddfccfa5 Mon Sep 17 00:00:00 2001 From: matcane Date: Sun, 27 Sep 2026 16:24:52 +0200 Subject: [PATCH 2/7] test(slider): add slider labels tests --- src/ImageComparisonSlider.test.tsx | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/ImageComparisonSlider.test.tsx b/src/ImageComparisonSlider.test.tsx index f9fba07..d00145f 100644 --- a/src/ImageComparisonSlider.test.tsx +++ b/src/ImageComparisonSlider.test.tsx @@ -75,6 +75,8 @@ describe("ImageComparisonSlider", () => { renderLine: Custom line, beforeImageProps: { testID: "before-image" }, afterImageProps: { testID: "after-image" }, + beforeLabel: "Before", + afterLabel: "After", }); expect(getSlider()).toHaveProp("accessibilityState", { disabled: true }); @@ -83,6 +85,8 @@ describe("ImageComparisonSlider", () => { expect(screen.queryByTestId("before-image")).toBeNull(); expect(screen.queryByText("Custom knob")).toBeNull(); expect(screen.queryByText("Custom line")).toBeNull(); + expect(screen.queryByText("Before")).toBeNull(); + expect(screen.queryByText("After")).toBeNull(); }); it("renders custom knob and line content", async () => { @@ -132,4 +136,34 @@ describe("ImageComparisonSlider", () => { expect(getSlider()).toHaveProp("accessibilityValue", { min: 0, max: 100, now: 50 }); }); + + it("does not render labels by default", async () => { + await renderSlider(); + + expect(screen.queryByTestId("before-label")).toBeNull(); + expect(screen.queryByTestId("after-label")).toBeNull(); + }); + + it("renders before and after labels when provided", async () => { + await renderSlider({ beforeLabel: "Before", afterLabel: "After" }); + await layoutSlider(); + + expect(screen.getByText("Before")).toBeOnTheScreen(); + expect(screen.getByText("After")).toBeOnTheScreen(); + }); + + it("renders only the before label", async () => { + await renderSlider({ beforeLabel: "Before" }); + await layoutSlider(); + + expect(screen.getByText("Before")).toBeOnTheScreen(); + expect(screen.queryByTestId("after-label")).toBeNull(); + }); + + it("renders only the after label", async () => { + await renderSlider({ afterLabel: "After" }); + + expect(screen.getByText("After")).toBeOnTheScreen(); + expect(screen.queryByTestId("before-label")).toBeNull(); + }); }); From 38fef804dc099c7a010298b1ea1493a4ed719f4a Mon Sep 17 00:00:00 2001 From: matcane Date: Sun, 27 Sep 2026 19:39:22 +0200 Subject: [PATCH 3/7] feat(slider): add Label component --- src/BeforeClip.tsx | 28 +++++++++++++++++++--------- src/ImageComparisonSlider.tsx | 5 +++++ src/Label.tsx | 31 +++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 src/Label.tsx diff --git a/src/BeforeClip.tsx b/src/BeforeClip.tsx index f0696dc..5fccbd9 100644 --- a/src/BeforeClip.tsx +++ b/src/BeforeClip.tsx @@ -1,7 +1,9 @@ import { Image, type ImageProps, type ImageSource } from "expo-image"; -import { StyleSheet } from "react-native"; +import { StyleSheet, View } from "react-native"; import Animated, { useAnimatedStyle, type DerivedValue } from "react-native-reanimated"; +import { Label } from "./Label"; + type PassThroughImageProps = Omit; interface BeforeClipProps { @@ -10,23 +12,31 @@ interface BeforeClipProps { imageProps?: PassThroughImageProps; measuredWidth: number; splitPx: DerivedValue; + beforeLabel?: string; } export function BeforeClip(props: BeforeClipProps) { - const { measuredWidth, splitPx, source, contentFit, imageProps } = props; + const { measuredWidth, splitPx, source, contentFit, imageProps, beforeLabel } = props; const clipStyle = useAnimatedStyle(() => ({ width: splitPx.get() })); return ( {measuredWidth > 0 && ( - + <> + + {beforeLabel && ( + + + )} + )} ); diff --git a/src/ImageComparisonSlider.tsx b/src/ImageComparisonSlider.tsx index b986bbf..4da5450 100644 --- a/src/ImageComparisonSlider.tsx +++ b/src/ImageComparisonSlider.tsx @@ -6,6 +6,7 @@ import { useDerivedValue } from "react-native-reanimated"; import { BeforeClip } from "./BeforeClip"; import { Handle } from "./Handle"; +import { Label } from "./Label"; import { useComparisonSlider } from "./useComparisonSlider"; type PassThroughImageProps = Omit; @@ -69,6 +70,8 @@ export function ImageComparisonSlider({ renderLine, beforeImageProps, afterImageProps, + beforeLabel, + afterLabel, }: ImageComparisonSliderProps) { const { measuredWidth, @@ -103,6 +106,7 @@ export function ImageComparisonSlider({ contentFit={contentFit} style={styles.fullImage} /> + {afterLabel && enabled &&