import React from "react"; import { I18nManager, StyleSheet, Text, View } from "react-native"; import Animated, { SharedValue, useAnimatedStyle, useDerivedValue, useSharedValue, } from "react-native-reanimated"; import { ThemeType, useTheme } from "../../theme"; const LABEL_HEIGHT = 26; const LABEL_PADDING = 8; const MIN_GAP = 4; interface SliderLabelsProps { leftValue: string; rightValue: string; leftPosition: SharedValue; rightPosition: SharedValue; sliderWidth: number; thumbSize: number; theme?: ThemeType; } interface SingleLabelProps { value: string; position: SharedValue; sliderWidth: number; onWidthChange: (width: number) => void; isVisible: SharedValue; theme: ThemeType; } interface MergedLabelProps { leftValue: string; rightValue: string; leftPosition: SharedValue; rightPosition: SharedValue; sliderWidth: number; isVisible: SharedValue; theme: ThemeType; } const SingleLabel: React.FC = ({ value, position, sliderWidth, onWidthChange, isVisible, theme, }) => { const styles = getStyles(theme); const localWidth = useSharedValue(0); const animatedStyle = useAnimatedStyle(() => { const halfLabel = localWidth.value / 2; const rawCenter = position.value; const shifted = rawCenter - halfLabel; const left = Math.min(Math.max(shifted, 0), sliderWidth - localWidth.value); return { left, opacity: isVisible.value ? 1 : 0, }; }); return ( { const width = e.nativeEvent.layout.width; localWidth.value = width; onWidthChange(width); }} > {value} ); }; const MergedLabel: React.FC = ({ leftValue, rightValue, leftPosition, rightPosition, sliderWidth, isVisible, theme, }) => { const styles = getStyles(theme); const labelWidth = useSharedValue(0); const animatedStyle = useAnimatedStyle(() => { const centerPosition = (leftPosition.value + rightPosition.value) / 2; const halfLabel = labelWidth.value / 2; const shifted = centerPosition - halfLabel; const left = Math.min(Math.max(shifted, 0), sliderWidth - labelWidth.value); return { left, opacity: isVisible.value ? 1 : 0, }; }); const displayText = leftValue === rightValue ? leftValue : `${leftValue} - ${rightValue}`; return ( { labelWidth.value = e.nativeEvent.layout.width; }} > {displayText} ); }; export const SliderLabels: React.FC = ({ leftValue, rightValue, leftPosition, rightPosition, sliderWidth, thumbSize, theme, }) => { const { theme: defaultTheme } = useTheme(); const currTheme = theme || defaultTheme; const isRTL = I18nManager.isRTL; // Track label widths in parent for overlap calculation const leftLabelWidth = useSharedValue(0); const rightLabelWidth = useSharedValue(0); const shouldMerge = useDerivedValue(() => { const leftCenter = leftPosition.value; const rightCenter = rightPosition.value; const leftEnd = leftCenter + leftLabelWidth.value / 2; const rightStart = rightCenter - rightLabelWidth.value / 2; if (isRTL) { const rtlLeftEnd = rightCenter + rightLabelWidth.value / 2; const rtlRightStart = leftCenter - leftLabelWidth.value / 2; return rtlRightStart - rtlLeftEnd < MIN_GAP; } return rightStart - leftEnd < MIN_GAP; }); const showSeparate = useDerivedValue(() => !shouldMerge.value); const showMerged = useDerivedValue(() => shouldMerge.value); return ( { leftLabelWidth.value = width; }} isVisible={showSeparate} theme={currTheme} /> { rightLabelWidth.value = width; }} isVisible={showSeparate} theme={currTheme} /> ); }; const getStyles = (theme: ThemeType) => StyleSheet.create({ wrapper: { position: "absolute", width: "100%", height: "100%", }, labelContainer: { position: "absolute", top: -LABEL_HEIGHT + 4, alignItems: "center", }, labelText: { backgroundColor: theme.surface, paddingHorizontal: LABEL_PADDING, paddingVertical: 4, borderRadius: 4, color: theme.onSurface, fontSize: 14, fontFamily: "DinBold", }, });