related-ui-components 3.2.9 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,16 @@
1
1
  import React from "react";
2
2
  import { I18nManager, StyleSheet, Text, View } from "react-native";
3
3
  import Animated, {
4
- SharedValue,
5
- useAnimatedStyle,
6
- useDerivedValue,
7
- useSharedValue,
4
+ SharedValue,
5
+ useAnimatedStyle,
6
+ useDerivedValue,
7
+ useSharedValue,
8
8
  } from "react-native-reanimated";
9
9
  import { ThemeType, useTheme } from "../../theme";
10
10
 
11
11
  const LABEL_HEIGHT = 26;
12
+ const LABEL_PADDING = 8;
13
+ const MIN_GAP = 4;
12
14
 
13
15
  interface SliderLabelsProps {
14
16
  leftValue: string;
@@ -17,51 +19,100 @@ interface SliderLabelsProps {
17
19
  rightPosition: SharedValue<number>;
18
20
  sliderWidth: number;
19
21
  thumbSize: number;
20
- theme?: ThemeType
22
+ theme?: ThemeType;
21
23
  }
22
24
 
23
- interface CenteredLabelProps {
25
+ interface SingleLabelProps {
24
26
  value: string;
25
27
  position: SharedValue<number>;
26
28
  sliderWidth: number;
27
- thumbSize: number;
28
- theme?: ThemeType
29
+ onWidthChange: (width: number) => void;
30
+ isVisible: SharedValue<boolean>;
31
+ theme: ThemeType;
32
+ }
29
33
 
34
+ interface MergedLabelProps {
35
+ leftValue: string;
36
+ rightValue: string;
37
+ leftPosition: SharedValue<number>;
38
+ rightPosition: SharedValue<number>;
39
+ sliderWidth: number;
40
+ isVisible: SharedValue<boolean>;
41
+ theme: ThemeType;
30
42
  }
31
43
 
32
- const CenteredLabel: React.FC<CenteredLabelProps> = ({
44
+ const SingleLabel: React.FC<SingleLabelProps> = ({
33
45
  value,
34
46
  position,
35
47
  sliderWidth,
36
- thumbSize,
37
- theme
48
+ onWidthChange,
49
+ isVisible,
50
+ theme,
38
51
  }) => {
39
- const labelWidth = useSharedValue(0);
40
- const {theme: defaultTheme} = useTheme();
41
- const currTheme = theme || defaultTheme;
42
- const styles = getStyles(currTheme);
52
+ const styles = getStyles(theme);
53
+ const localWidth = useSharedValue(0);
43
54
 
44
- const animatedLabelStyle = useAnimatedStyle(() => {
45
- const rawCenter = position.value + thumbSize / 2;
46
- const shifted = rawCenter - labelWidth.value / 2;
55
+ const animatedStyle = useAnimatedStyle(() => {
56
+ const halfLabel = localWidth.value / 2;
57
+ const rawCenter = position.value;
58
+ const shifted = rawCenter - halfLabel;
59
+ const left = Math.min(Math.max(shifted, 0), sliderWidth - localWidth.value);
47
60
 
61
+ return {
62
+ left,
63
+ opacity: isVisible.value ? 1 : 0,
64
+ };
65
+ });
66
+
67
+ return (
68
+ <Animated.View
69
+ style={[styles.labelContainer, animatedStyle]}
70
+ onLayout={(e) => {
71
+ const width = e.nativeEvent.layout.width;
72
+ localWidth.value = width;
73
+ onWidthChange(width);
74
+ }}
75
+ >
76
+ <Text style={styles.labelText}>{value}</Text>
77
+ </Animated.View>
78
+ );
79
+ };
80
+
81
+ const MergedLabel: React.FC<MergedLabelProps> = ({
82
+ leftValue,
83
+ rightValue,
84
+ leftPosition,
85
+ rightPosition,
86
+ sliderWidth,
87
+ isVisible,
88
+ theme,
89
+ }) => {
90
+ const styles = getStyles(theme);
91
+ const labelWidth = useSharedValue(0);
92
+
93
+ const animatedStyle = useAnimatedStyle(() => {
94
+ const centerPosition = (leftPosition.value + rightPosition.value) / 2;
95
+ const halfLabel = labelWidth.value / 2;
96
+ const shifted = centerPosition - halfLabel;
48
97
  const left = Math.min(Math.max(shifted, 0), sliderWidth - labelWidth.value);
49
98
 
50
99
  return {
51
- left: left,
100
+ left,
101
+ opacity: isVisible.value ? 1 : 0,
52
102
  };
53
103
  });
54
104
 
105
+ const displayText =
106
+ leftValue === rightValue ? leftValue : `${leftValue} - ${rightValue}`;
107
+
55
108
  return (
56
109
  <Animated.View
57
- style={[styles.labelContainer, animatedLabelStyle]}
110
+ style={[styles.labelContainer, animatedStyle]}
58
111
  onLayout={(e) => {
59
112
  labelWidth.value = e.nativeEvent.layout.width;
60
113
  }}
61
114
  >
62
- <Text style={styles.labelText}>
63
- {value}
64
- </Text>
115
+ <Text style={styles.labelText}>{displayText}</Text>
65
116
  </Animated.View>
66
117
  );
67
118
  };
@@ -77,29 +128,62 @@ export const SliderLabels: React.FC<SliderLabelsProps> = ({
77
128
  }) => {
78
129
  const { theme: defaultTheme } = useTheme();
79
130
  const currTheme = theme || defaultTheme;
80
- const styles = getStyles(currTheme);
81
- const rightThumbLeftPosition = useDerivedValue(() => {
82
- return rightPosition.value - thumbSize;
83
- });
84
- const leftThumbLeftPosition = useDerivedValue(() => {
85
- return leftPosition.value - thumbSize;
131
+ const isRTL = I18nManager.isRTL;
132
+
133
+ // Track label widths in parent for overlap calculation
134
+ const leftLabelWidth = useSharedValue(0);
135
+ const rightLabelWidth = useSharedValue(0);
136
+
137
+ const shouldMerge = useDerivedValue(() => {
138
+ const leftCenter = leftPosition.value;
139
+ const rightCenter = rightPosition.value;
140
+
141
+ const leftEnd = leftCenter + leftLabelWidth.value / 2;
142
+ const rightStart = rightCenter - rightLabelWidth.value / 2;
143
+
144
+ if (isRTL) {
145
+ const rtlLeftEnd = rightCenter + rightLabelWidth.value / 2;
146
+ const rtlRightStart = leftCenter - leftLabelWidth.value / 2;
147
+ return rtlRightStart - rtlLeftEnd < MIN_GAP;
148
+ }
149
+
150
+ return rightStart - leftEnd < MIN_GAP;
86
151
  });
87
152
 
88
- const isRTL = I18nManager.isRTL;
153
+ const showSeparate = useDerivedValue(() => !shouldMerge.value);
154
+ const showMerged = useDerivedValue(() => shouldMerge.value);
89
155
 
90
156
  return (
91
- <View style={styles.wrapper} pointerEvents="none">
92
- <CenteredLabel
157
+ <View style={getStyles(currTheme).wrapper} pointerEvents="none">
158
+ <SingleLabel
93
159
  value={leftValue}
94
- thumbSize={thumbSize}
95
- position={isRTL ? leftThumbLeftPosition : leftPosition}
160
+ position={leftPosition}
96
161
  sliderWidth={sliderWidth}
162
+ onWidthChange={(width) => {
163
+ leftLabelWidth.value = width;
164
+ }}
165
+ isVisible={showSeparate}
166
+ theme={currTheme}
97
167
  />
98
- <CenteredLabel
168
+ <SingleLabel
99
169
  value={rightValue}
100
- thumbSize={thumbSize}
101
- position={isRTL ? rightPosition : rightThumbLeftPosition}
170
+ position={rightPosition}
171
+ sliderWidth={sliderWidth}
172
+ onWidthChange={(width) => {
173
+ rightLabelWidth.value = width;
174
+ }}
175
+ isVisible={showSeparate}
176
+ theme={currTheme}
177
+ />
178
+
179
+ <MergedLabel
180
+ leftValue={leftValue}
181
+ rightValue={rightValue}
182
+ leftPosition={leftPosition}
183
+ rightPosition={rightPosition}
102
184
  sliderWidth={sliderWidth}
185
+ isVisible={showMerged}
186
+ theme={currTheme}
103
187
  />
104
188
  </View>
105
189
  );
@@ -119,7 +203,7 @@ const getStyles = (theme: ThemeType) =>
119
203
  },
120
204
  labelText: {
121
205
  backgroundColor: theme.surface,
122
- paddingHorizontal: 8,
206
+ paddingHorizontal: LABEL_PADDING,
123
207
  paddingVertical: 4,
124
208
  borderRadius: 4,
125
209
  color: theme.onSurface,