diff --git a/README.md b/README.md
index 8769ef1..89133e3 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,8 @@ const styles = StyleSheet.create({
| `renderLine` | `ReactNode` | — | Divider line |
| `beforeImageProps` | `PassThroughImageProps` | — | `before` (`expo-image`, minus `source` / `style` / `contentFit`) |
| `afterImageProps` | `PassThroughImageProps` | — | `after` |
+| `beforeLabel` | `string` | — | `Label on the before image` |
+| `afterLabel` | `string` | — | `Label on the after image` |
## License
diff --git a/example/App.tsx b/example/App.tsx
index 1e9ed8d..3e4c2de 100644
--- a/example/App.tsx
+++ b/example/App.tsx
@@ -8,7 +8,9 @@ export default function App() {
}
@@ -16,7 +18,9 @@ export default function App() {
}
diff --git a/src/BeforeClip.tsx b/src/BeforeClip.tsx
index f0696dc..0d8407e 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.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();
+ });
});
diff --git a/src/ImageComparisonSlider.tsx b/src/ImageComparisonSlider.tsx
index 0be5890..30d9ccf 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;
@@ -44,6 +45,12 @@ export interface ImageComparisonSliderProps {
/** after */
afterImageProps?: PassThroughImageProps;
+
+ /** Label on the before image */
+ beforeLabel?: string;
+
+ /** Label on the after image */
+ afterLabel?: string;
}
/**
@@ -63,6 +70,8 @@ export function ImageComparisonSlider({
renderLine,
beforeImageProps,
afterImageProps,
+ beforeLabel,
+ afterLabel,
}: ImageComparisonSliderProps) {
const {
measuredWidth,
@@ -97,6 +106,7 @@ export function ImageComparisonSlider({
contentFit={contentFit}
style={styles.fullImage}
/>
+ {afterLabel && enabled && }
{enabled && (
)}
diff --git a/src/Label.tsx b/src/Label.tsx
new file mode 100644
index 0000000..c4edb8d
--- /dev/null
+++ b/src/Label.tsx
@@ -0,0 +1,32 @@
+import { StyleSheet, Text, View, type ViewStyle } from "react-native";
+
+import { EDGE_MARGIN } from "./utils";
+
+interface LabelProps {
+ text: string;
+ alignSelf?: ViewStyle["alignSelf"];
+ testID?: string;
+}
+
+export function Label({ text, alignSelf = "flex-end", testID }: LabelProps) {
+ return (
+
+ {text}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ badge: {
+ margin: EDGE_MARGIN,
+ backgroundColor: "white",
+ paddingHorizontal: 8,
+ paddingVertical: 2,
+ borderRadius: 8,
+ },
+ text: {
+ color: "black",
+ fontSize: 12,
+ lineHeight: 16,
+ },
+});