related-ui-components 3.2.8 → 3.3.0

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.
@@ -22,55 +22,25 @@ export interface SpinWheelItem {
22
22
 
23
23
  //default random colors
24
24
  const colors = [
25
- "#FF0000", // Red
26
- "#FFA500", // Orange
27
- "#FFFF00", // Yellow
28
- "#008000", // Green
29
- "#0000FF", // Blue
30
- "#800080", // Purple
31
- "#FFC0CB", // Pink
32
- "#00FFFF", // Cyan
33
- "#FF00FF", // Magenta
34
- "#00FF00", // Lime
35
- "#4B0082", // Indigo
36
- "#EE82EE", // Violet
37
- "#40E0D0", // Turquoise
38
- "#FFD700", // Gold
39
- "#C0C0C0", // Silver
40
- "#FFDAB9", // Peach
41
- "#E6E6FA", // Lavender
42
- "#008080", // Teal
43
- "#FF7F50", // Coral
44
- "#DC143C", // Crimson
45
- "#87CEEB", // Sky Blue
46
- "#7FFF00", // Chartreuse
47
- "#CCCCFF", // Periwinkle
48
- "#FF6347", // Tomato
49
- "#FA8072", // Salmon
25
+ "#FF0000", "#FFA500", "#FFFF00", "#008000", "#0000FF", "#800080",
26
+ "#FFC0CB", "#00FFFF", "#FF00FF", "#00FF00", "#4B0082", "#EE82EE",
27
+ "#40E0D0", "#FFD700", "#C0C0C0", "#FFDAB9", "#E6E6FA", "#008080",
28
+ "#FF7F50", "#DC143C", "#87CEEB", "#7FFF00", "#CCCCFF", "#FF6347",
29
+ "#FA8072",
50
30
  ];
51
31
 
52
32
  interface SpinWheelProps {
53
- // Data
54
33
  items: SpinWheelItem[];
55
34
  predeterminedWinner?: SpinWheelItem | string | number;
56
-
57
- // Dimensions
58
35
  size?: number;
59
-
60
- startSpin?: boolean; // start spinning immediately
61
- winner?: SpinWheelItem | string | number; // winner from API
62
- minSpinDuration?: number; // buffer in ms before stopping
63
-
64
- // Behavior
36
+ startSpin?: boolean;
37
+ winner?: SpinWheelItem | string | number;
38
+ minSpinDuration?: number;
65
39
  spinDuration?: number;
66
40
  friction?: number;
67
41
  enabled?: boolean;
68
-
69
- // Events
70
42
  onSpinStart?: () => void;
71
43
  onSpinEnd?: (item: SpinWheelItem) => void;
72
-
73
- // Styling
74
44
  containerStyle?: ViewStyle;
75
45
  centerStyle?: ViewStyle;
76
46
  spinButtonText?: string;
@@ -79,17 +49,19 @@ interface SpinWheelProps {
79
49
  actionButtonStyle?: ViewStyle;
80
50
  actionButtonTextStyle?: TextStyle;
81
51
  actionButtonProps?: Partial<Omit<AppButtonProps, "title" | "onPress">>;
82
-
83
52
  wheelBorderColor?: string;
84
53
  wheelBorderWidth?: number;
85
54
  wheelTextColor?: string;
86
55
  knobColor?: string;
87
-
88
56
  centerSize?: number;
89
-
90
- centerComponent?: React.ReactNode; // Note: centerComponent prop is declared but not used
57
+ centerComponent?: React.ReactNode;
91
58
  }
92
59
 
60
+ // Constants for consistent animation
61
+ const ROTATION_DURATION = 2500; // Duration for one full rotation
62
+ const DECEL_DURATION = 3000; // Duration for deceleration phase
63
+ const EXTRA_SPINS = 3; // Number of extra full rotations before stopping
64
+
93
65
  const SpinWheel: React.FC<SpinWheelProps> = ({
94
66
  items,
95
67
  size = 300,
@@ -119,19 +91,16 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
119
91
 
120
92
  const [spinning, setSpinning] = useState(false);
121
93
  const rotateValue = useRef(new Animated.Value(0)).current;
122
-
123
- // Track rotation manually for calculations
124
94
  const rotationRef = useRef(0);
125
95
 
126
96
  const startInfiniteSpin = () => {
127
97
  rotateValue.setValue(rotationRef.current);
128
-
129
98
  spinStartTime.current = Date.now();
130
99
 
131
100
  spinLoop.current = Animated.loop(
132
101
  Animated.timing(rotateValue, {
133
- toValue: rotationRef.current + 1, // 1 = full rotation
134
- duration: 2500,
102
+ toValue: rotationRef.current + 1,
103
+ duration: ROTATION_DURATION,
135
104
  easing: Easing.linear,
136
105
  useNativeDriver: true,
137
106
  })
@@ -154,26 +123,38 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
154
123
  const index = items.findIndex((i) => i.id === winnerId);
155
124
  if (index === -1) return;
156
125
 
126
+ // Calculate target angle (pointer at top = 270 degrees)
127
+ const anglePerItem = 360 / items.length;
157
128
  const segmentCenter = (index + 0.5) * anglePerItem;
158
129
  const targetAngle = (270 - segmentCenter + 360) % 360;
159
130
 
160
- const currentAngle = (rotationRef.current * 360) % 360;
161
- const offset = (targetAngle - currentAngle + 360) % 360;
162
-
163
- const finalRotation = rotationRef.current + (offset + 3 * 360) / 360;
164
-
131
+ // Get current rotation position
132
+ const currentRotation = rotationRef.current;
133
+ const currentAngle = (currentRotation * 360) % 360;
134
+
135
+ // Calculate shortest path to target
136
+ let offset = (targetAngle - currentAngle + 360) % 360;
137
+
138
+ // Add extra full rotations for dramatic effect
139
+ const extraRotations = EXTRA_SPINS * 360;
140
+ const totalRotation = offset + extraRotations;
141
+
142
+ // Final rotation value
143
+ const finalRotation = currentRotation + totalRotation / 360;
144
+
145
+ // Use ease-out cubic for smooth deceleration
165
146
  Animated.timing(rotateValue, {
166
147
  toValue: finalRotation,
167
- duration: 2500,
148
+ duration: DECEL_DURATION,
168
149
  easing: Easing.out(Easing.cubic),
169
150
  useNativeDriver: true,
170
151
  }).start(() => {
152
+ rotationRef.current = finalRotation;
171
153
  setSpinning(false);
172
154
  onSpinEnd?.(items[index]);
173
155
  });
174
156
  };
175
157
 
176
- // Update tracked rotation when animation completes
177
158
  useEffect(() => {
178
159
  const listener = rotateValue.addListener(({ value }) => {
179
160
  rotationRef.current = value;
@@ -184,10 +165,8 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
184
165
  };
185
166
  }, [rotateValue]);
186
167
 
187
- // Calculate angle for each segment
188
168
  const anglePerItem = wheelItems.length > 0 ? 360 / wheelItems.length : 0;
189
169
 
190
- // Create wheel segments
191
170
  const generateWheelPaths = () => {
192
171
  if (wheelItems.length === 0) return [];
193
172
  return wheelItems.map((item, index) => {
@@ -215,17 +194,11 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
215
194
  const textX = size / 2 + size * 0.32 * Math.cos(midRad);
216
195
  const textY = size / 2 + size * 0.32 * Math.sin(midRad);
217
196
 
218
- // decorationX and decorationY are calculated but not used in the provided JSX
219
- // const decorationX = size / 2 + size * 0.43 * Math.cos(midRad);
220
- // const decorationY = size / 2 + size * 0.43 * Math.sin(midRad);
221
-
222
197
  return {
223
198
  path: pathData,
224
199
  item,
225
200
  textX,
226
201
  textY,
227
- // decorationX,
228
- // decorationY,
229
202
  angle: (startAngle + endAngle) / 2,
230
203
  };
231
204
  });
@@ -244,9 +217,8 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
244
217
  }, delay);
245
218
 
246
219
  return () => clearTimeout(timeout);
247
- }, [winner]);
220
+ }, [winner, spinning]);
248
221
 
249
- // Animation interpolation for rotation
250
222
  const rotate = rotateValue.interpolate({
251
223
  inputRange: [0, 1],
252
224
  outputRange: ["0deg", "360deg"],
@@ -257,7 +229,6 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
257
229
  return (
258
230
  <View style={[styles.container, containerStyle]}>
259
231
  <View style={{ width: size, height: size }}>
260
- {/* The wheel */}
261
232
  <Animated.View
262
233
  style={[
263
234
  styles.wheelContainer,
@@ -271,7 +242,7 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
271
242
  >
272
243
  <Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
273
244
  <G>
274
- {wheelPaths.map(({ path, item, textX, textY, angle }, index) => {
245
+ {wheelPaths.map(({ path, item, textX, textY, angle }) => {
275
246
  return (
276
247
  <React.Fragment key={item.id}>
277
248
  <Path
@@ -281,8 +252,6 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
281
252
  ? colors[Math.floor(Math.random() * colors.length)]
282
253
  : item.color
283
254
  }
284
- // stroke="#FA8072"
285
- // strokeWidth={1}
286
255
  />
287
256
  <SvgText
288
257
  x={textX}
@@ -292,7 +261,7 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
292
261
  fontWeight={(wheelTextStyle?.fontWeight as any) || "bold"}
293
262
  textAnchor="middle"
294
263
  alignmentBaseline="central"
295
- transform={`rotate(${angle + 180}, ${textX}, ${textY} )`}
264
+ transform={`rotate(${angle + 180}, ${textX}, ${textY})`}
296
265
  >
297
266
  {item.label}
298
267
  </SvgText>
@@ -314,14 +283,12 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
314
283
  </Svg>
315
284
  </Animated.View>
316
285
 
317
- {/* The center circle */}
318
286
  {centerComponent ? (
319
287
  <View
320
288
  style={{
321
289
  position: "absolute",
322
290
  top: "50%",
323
291
  left: "50%",
324
- // Center the component perfectly
325
292
  transform: [
326
293
  { translateX: -(actualCenterSize / 2) },
327
294
  { translateY: -(actualCenterSize / 2) },
@@ -349,7 +316,6 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
349
316
  />
350
317
  )}
351
318
 
352
- {/* The pointer is a triangle on top */}
353
319
  <View
354
320
  style={[
355
321
  styles.pointerPosition,
@@ -365,7 +331,6 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
365
331
  />
366
332
  </View>
367
333
 
368
- {/* Action Button */}
369
334
  <View
370
335
  style={{
371
336
  position: "absolute",
@@ -384,15 +349,6 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
384
349
  textStyle={[styles.actionButtonText, actionButtonTextStyle]}
385
350
  {...actionButtonProps}
386
351
  />
387
- {/* <TouchableOpacity
388
- onPress={handleSpin}
389
- disabled={spinning || !enabled || wheelItems.length === 0}
390
- style={[styles.actionButton, actionButtonStyle]}
391
- >
392
- <Text style={[styles.actionButtonText, actionButtonTextStyle]}>
393
- {spinButtonText}
394
- </Text>
395
- </TouchableOpacity> */}
396
352
  </View>
397
353
  </View>
398
354
  </View>
@@ -404,7 +360,7 @@ const styles = StyleSheet.create({
404
360
  alignItems: "center",
405
361
  justifyContent: "center",
406
362
  marginTop: 20,
407
- marginBottom: 70, // Space for the button
363
+ marginBottom: 70,
408
364
  },
409
365
  wheelContainer: {
410
366
  overflow: "hidden",