From 6cf122fad82dd8fb6985bee95cc964cb064be10d Mon Sep 17 00:00:00 2001 From: matcane Date: Sat, 19 Sep 2026 17:40:02 +0200 Subject: [PATCH] refactor(slider): extract BeforeClip and Handle --- src/BeforeClip.tsx | 49 ++++++++++++++++++++++ src/Handle.tsx | 56 +++++++++++++++++++++++++ src/ImageComparisonSlider.tsx | 77 +++++------------------------------ 3 files changed, 116 insertions(+), 66 deletions(-) create mode 100644 src/BeforeClip.tsx create mode 100644 src/Handle.tsx diff --git a/src/BeforeClip.tsx b/src/BeforeClip.tsx new file mode 100644 index 0000000..f0696dc --- /dev/null +++ b/src/BeforeClip.tsx @@ -0,0 +1,49 @@ +import { Image, type ImageProps, type ImageSource } from "expo-image"; +import { StyleSheet } from "react-native"; +import Animated, { useAnimatedStyle, type DerivedValue } from "react-native-reanimated"; + +type PassThroughImageProps = Omit; + +interface BeforeClipProps { + source: ImageSource | number; + contentFit?: ImageProps["contentFit"]; + imageProps?: PassThroughImageProps; + measuredWidth: number; + splitPx: DerivedValue; +} + +export function BeforeClip(props: BeforeClipProps) { + const { measuredWidth, splitPx, source, contentFit, imageProps } = props; + + const clipStyle = useAnimatedStyle(() => ({ width: splitPx.get() })); + + return ( + + {measuredWidth > 0 && ( + + )} + + ); +} + +const styles = StyleSheet.create({ + clip: { + position: "absolute", + left: 0, + top: 0, + bottom: 0, + overflow: "hidden", + }, + leftImage: { + position: "absolute", + left: 0, + top: 0, + height: "100%", + }, +}); diff --git a/src/Handle.tsx b/src/Handle.tsx new file mode 100644 index 0000000..c5d0761 --- /dev/null +++ b/src/Handle.tsx @@ -0,0 +1,56 @@ +import type { ReactNode } from "react"; +import { StyleSheet, View } from "react-native"; +import Animated, { useAnimatedStyle, type DerivedValue } from "react-native-reanimated"; + +const HANDLE_KNOB = 40; +const LINE_WIDTH = 3; + +interface HandleProps { + knobContent?: ReactNode; + renderLine?: ReactNode; + splitPx: DerivedValue; +} + +export function Handle(props: HandleProps) { + const { knobContent, renderLine, splitPx } = props; + + const handleGroupStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: splitPx.get() - HANDLE_KNOB / 2 }], + })); + + return ( + + {renderLine ?? } + {knobContent} + + ); +} + +const styles = StyleSheet.create({ + handleColumn: { + position: "absolute", + left: 0, + top: 0, + bottom: 0, + width: HANDLE_KNOB, + alignItems: "center", + justifyContent: "center", + }, + line: { + position: "absolute", + top: 0, + bottom: 0, + width: LINE_WIDTH, + alignSelf: "center", + backgroundColor: "#ffffff", + }, + knob: { + width: HANDLE_KNOB, + height: HANDLE_KNOB, + borderRadius: HANDLE_KNOB / 2, + overflow: "hidden", + alignItems: "center", + justifyContent: "center", + backgroundColor: "#ffffff", + }, +}); diff --git a/src/ImageComparisonSlider.tsx b/src/ImageComparisonSlider.tsx index 5219369..0be5890 100644 --- a/src/ImageComparisonSlider.tsx +++ b/src/ImageComparisonSlider.tsx @@ -2,13 +2,12 @@ import { Image, type ImageProps, type ImageSource } from "expo-image"; import { type ReactNode } from "react"; import { StyleSheet, View, type StyleProp, type ViewStyle } from "react-native"; import { GestureDetector } from "react-native-gesture-handler"; -import Animated, { useAnimatedStyle, useDerivedValue } from "react-native-reanimated"; +import { useDerivedValue } from "react-native-reanimated"; +import { BeforeClip } from "./BeforeClip"; +import { Handle } from "./Handle"; import { useComparisonSlider } from "./useComparisonSlider"; -const HANDLE_KNOB = 40; -const LINE_WIDTH = 3; - type PassThroughImageProps = Omit; /** Props for {@link ImageComparisonSlider}. */ @@ -77,12 +76,6 @@ export function ImageComparisonSlider({ const splitPx = useDerivedValue(() => splitRatio.get() * containerWidth.get()); - const clipStyle = useAnimatedStyle(() => ({ width: splitPx.get() })); - - const handleGroupStyle = useAnimatedStyle(() => ({ - transform: [{ translateX: splitPx.get() - HANDLE_KNOB / 2 }], - })); - return ( {enabled && ( - - {measuredWidth > 0 && ( - - )} - + )} - {enabled && ( - - {renderLine ?? } - {knobContent} - - )} + {enabled && } ); @@ -143,43 +127,4 @@ const styles = StyleSheet.create({ width: "100%", height: "100%", }, - clip: { - position: "absolute", - left: 0, - top: 0, - bottom: 0, - overflow: "hidden", - }, - leftImage: { - position: "absolute", - left: 0, - top: 0, - height: "100%", - }, - handleColumn: { - position: "absolute", - left: 0, - top: 0, - bottom: 0, - width: HANDLE_KNOB, - alignItems: "center", - justifyContent: "center", - }, - line: { - position: "absolute", - top: 0, - bottom: 0, - width: LINE_WIDTH, - alignSelf: "center", - backgroundColor: "#ffffff", - }, - knob: { - width: HANDLE_KNOB, - height: HANDLE_KNOB, - borderRadius: HANDLE_KNOB / 2, - overflow: "hidden", - alignItems: "center", - justifyContent: "center", - backgroundColor: "#ffffff", - }, });