related-ui-components 1.8.6 → 1.8.7

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,3 +1,4 @@
1
+ import "react-native-reanimated";
1
2
  export * from "./theme";
2
3
  export * from "./components";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAQA,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,yBAAyB,CAAC;AAOjC,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAC"}
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
- declare const MyScreen: () => React.JSX.Element;
3
- export default MyScreen;
2
+ declare const MockWheelScreen: () => React.JSX.Element;
3
+ export default MockWheelScreen;
4
4
  //# sourceMappingURL=app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/app.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AASxC,QAAA,MAAM,QAAQ,yBAiGb,CAAC;AAEF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/app.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAaxC,QAAA,MAAM,eAAe,yBA6BpB,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -9,8 +9,10 @@ export interface SpinWheelItem {
9
9
  }
10
10
  interface SpinWheelProps {
11
11
  items: SpinWheelItem[];
12
+ predeterminedWinner?: SpinWheelItem | string | number;
12
13
  size?: number;
13
14
  spinDuration?: number;
15
+ friction?: number;
14
16
  enabled?: boolean;
15
17
  onSpinStart?: () => void;
16
18
  onSpinEnd?: (item: SpinWheelItem) => void;
@@ -21,10 +23,10 @@ interface SpinWheelProps {
21
23
  knobStyle?: ViewStyle;
22
24
  actionButtonStyle?: ViewStyle;
23
25
  actionButtonTextStyle?: TextStyle;
26
+ wheelBorderColor?: string;
24
27
  wheelTextColor?: string;
25
28
  knobColor?: string;
26
29
  centerComponent?: React.ReactNode;
27
- winner?: SpinWheelItem | null;
28
30
  }
29
31
  declare const SpinWheel: React.FC<SpinWheelProps>;
30
32
  export default SpinWheel;
@@ -1 +1 @@
1
- {"version":3,"file":"Wheel.d.ts","sourceRoot":"","sources":["../../../../../src/components/Wheel/Wheel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAOL,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD,UAAU,cAAc;IACtB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC9B,qBAAqB,CAAC,EAAE,SAAS,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAqOvC,CAAC;AAsDF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"Wheel.d.ts","sourceRoot":"","sources":["../../../../../src/components/Wheel/Wheel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAOL,SAAS,EACT,SAAS,EACV,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+BD,UAAU,cAAc;IAEtB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,mBAAmB,CAAC,EAAE,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;IAGtD,IAAI,CAAC,EAAE,MAAM,CAAC;IAGd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAGlB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAG1C,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC9B,qBAAqB,CAAC,EAAE,SAAS,CAAC;IAGlC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACnC;AAED,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAwRvC,CAAC;AAqDF,eAAe,SAAS,CAAC"}
@@ -1,3 +1,4 @@
1
+ import "react-native-reanimated";
1
2
  export * from "./theme";
2
3
  export * from "./components";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAQA,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,yBAAyB,CAAC;AAOjC,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "related-ui-components",
3
3
  "main": "./src/index.ts",
4
- "version": "1.8.6",
4
+ "version": "1.8.7",
5
5
  "scripts": {
6
6
  "start": "expo start",
7
7
  "reset-project": "node ./scripts/reset-project.js",
package/src/app.tsx CHANGED
@@ -1,109 +1,45 @@
1
1
  import React, { useState } from "react";
2
2
  import { View, Button } from "react-native";
3
- import { FilterResult, Filters, Popup, UnlockRewards } from "./components";
4
- import BRANDS from "./constants/BRANDS";
5
- import { Ionicons } from "@expo/vector-icons";
6
- import { useTheme } from "./theme";
7
- import RedeemedVoucherSheet from "./components/RedeemedVoucher/RedeemedVoucherSheet";
8
- import { GestureHandlerRootView } from "react-native-gesture-handler";
3
+ import SpinWheel, { SpinWheelItem } from "./components/Wheel/Wheel";
9
4
 
10
- const MyScreen = () => {
11
- const [isFilterVisible, setIsFilterVisible] = useState(false);
5
+ const wheelItems: SpinWheelItem[] = [
6
+ { id: 1, label: "Prize 1", color: "#FF6384" },
7
+ { id: 2, label: "Prize 2", color: "#36A2EB" },
8
+ { id: 3, label: "Prize 3", color: "#FFCE56" },
9
+ { id: 4, label: "Prize 4", color: "#4BC0C0" },
10
+ { id: 5, label: "Prize 5", color: "#9966FF" },
11
+ { id: 6, label: "Prize 6", color: "#FF9F40" },
12
+ ];
12
13
 
13
- const { theme } = useTheme();
14
-
15
- const handleApplyFilters = (result: FilterResult) => {
16
- console.log("Filters applied:", result);
17
- // Process filter results...
18
- };
19
-
20
- const rewardsData = [
21
- {
22
- icon: (
23
- <Ionicons name="briefcase-outline" size={30} color={theme.primary} />
24
- ),
25
- activeIcon: (
26
- <Ionicons name="briefcase-outline" size={30} color={theme.onPrimary} />
27
- ),
28
- title: "Aqua Guardian",
29
- description:
30
- "Maintain water usage below the community average for a month.",
31
- isActive: false,
32
- status: "0/1",
33
- statusBackgroundColor: "#FFCDD2",
34
- statusTextColor: "#D32F2F",
35
- },
36
- {
37
- icon: <Ionicons name="heart-outline" size={30} color={theme.primary} />,
38
- activeIcon: (
39
- <Ionicons name="heart-outline" size={30} color={theme.onPrimary} />
40
- ),
41
- title: "Wellness Warrior",
42
- description: "Complete 30 days of healthy hydration tracking.",
43
- isActive: true,
44
- status: "15/30",
45
- statusBackgroundColor: "#C8E6C9",
46
- statusTextColor: "#388E3C",
47
- },
48
- {
49
- icon: <Ionicons name="airplane-outline" size={24} color={theme.helper} />,
50
- activeIcon: (
51
- <Ionicons name="airplane-outline" size={24} color={theme.primary} />
52
- ),
53
- title: "Eco Traveler",
54
- description: "Log 5 trips where you chose sustainable travel options.",
55
- isActive: false,
56
- status: "2/5",
57
- statusBackgroundColor: theme.disabled,
58
- statusTextColor: theme.text,
59
- },
60
- {
61
- icon: <Ionicons name="school-outline" size={24} color={theme.helper} />,
62
- activeIcon: (
63
- <Ionicons name="school-outline" size={24} color={theme.primary} />
64
- ),
65
- title: "Knowledge Seeker",
66
- description: "Complete all water conservation learning modules.",
67
- isActive: false,
68
- status: "3/5",
69
- statusBackgroundColor: theme.disabled,
70
- statusTextColor: theme.text,
71
- },
72
- {
73
- icon: <Ionicons name="settings-outline" size={24} color={theme.helper} />,
74
- activeIcon: (
75
- <Ionicons name="settings-outline" size={24} color={theme.primary} />
76
- ),
77
- title: "Smart Saver",
78
- description: "Set up and use all water-saving features in the app.",
79
- isActive: true,
80
- status: "4/4",
81
- statusBackgroundColor: theme.primary,
82
- statusTextColor: theme.background,
83
- },
84
- ];
14
+ const MockWheelScreen = () => {
15
+ const [winner, setWinner] = useState<SpinWheelItem | null>(null);
85
16
 
86
17
  return (
87
- <>
88
- <GestureHandlerRootView>
89
- <RedeemedVoucherSheet
90
- visible={true}
91
- onClose={() => {}}
92
- code="0000P78SHV50KK"
93
- // amount={100}
94
- expiry="22/01/2025"
95
- currency="USD"
96
- title="Voucher Activated!"
97
- // primaryButtonColors={["red", "blue"]}
98
- onMyVouchersButtonPress={() => console.log("My Vouchers pressed")}
99
- onCopyCode={(code) => console.log("Copied:", code)}
100
- // To use custom icons:
101
- // amountIconComponent={<Image source={YourCustomAmountIcon} style={{width: 20, height: 20, marginRight: 5}} />}
102
- // renderCopyIcon={() => <Text style={{fontSize: 22, color: 'blue'}}>COPY</Text>}
18
+ <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
19
+ <SpinWheel
20
+ items={wheelItems}
21
+ predeterminedWinner={wheelItems[2]}
22
+ onSpinEnd={(item) => {
23
+ alert(`Winner: ${item.label}`);
24
+ setWinner(null);
25
+ }}
26
+ size={300}
27
+ spinButtonText="Spin Randomly"
103
28
  />
104
- </GestureHandlerRootView>
105
- </>
29
+
30
+ <View style={{ flexDirection: "row", marginTop: 20 }}>
31
+ <Button
32
+ title="Spin to Prize 3"
33
+ onPress={() => setWinner(wheelItems[2])}
34
+ />
35
+ <View style={{ width: 20 }} />
36
+ <Button
37
+ title="Spin Random"
38
+ onPress={() => setWinner(null)}
39
+ />
40
+ </View>
41
+ </View>
106
42
  );
107
43
  };
108
44
 
109
- export default MyScreen;
45
+ export default MockWheelScreen;
@@ -19,20 +19,53 @@ export interface SpinWheelItem {
19
19
  textColor?: string;
20
20
  }
21
21
 
22
+ //default random colors
22
23
  const colors = [
23
- "#FF0000", "#FFA500", "#FFFF00", "#008000", "#0000FF", "#800080",
24
- "#FFC0CB", "#00FFFF", "#FF00FF", "#00FF00", "#4B0082", "#EE82EE",
25
- "#40E0D0", "#FFD700", "#C0C0C0", "#FFDAB9", "#E6E6FA", "#008080",
26
- "#FF7F50", "#DC143C", "#87CEEB", "#7FFF00", "#CCCCFF", "#FF6347", "#FA8072"
24
+ "#FF0000", // Red
25
+ "#FFA500", // Orange
26
+ "#FFFF00", // Yellow
27
+ "#008000", // Green
28
+ "#0000FF", // Blue
29
+ "#800080", // Purple
30
+ "#FFC0CB", // Pink
31
+ "#00FFFF", // Cyan
32
+ "#FF00FF", // Magenta
33
+ "#00FF00", // Lime
34
+ "#4B0082", // Indigo
35
+ "#EE82EE", // Violet
36
+ "#40E0D0", // Turquoise
37
+ "#FFD700", // Gold
38
+ "#C0C0C0", // Silver
39
+ "#FFDAB9", // Peach
40
+ "#E6E6FA", // Lavender
41
+ "#008080", // Teal
42
+ "#FF7F50", // Coral
43
+ "#DC143C", // Crimson
44
+ "#87CEEB", // Sky Blue
45
+ "#7FFF00", // Chartreuse
46
+ "#CCCCFF", // Periwinkle
47
+ "#FF6347", // Tomato
48
+ "#FA8072", // Salmon
27
49
  ];
28
50
 
29
51
  interface SpinWheelProps {
52
+ // Data
30
53
  items: SpinWheelItem[];
54
+ predeterminedWinner?: SpinWheelItem | string | number; // New prop
55
+
56
+ // Dimensions
31
57
  size?: number;
58
+
59
+ // Behavior
32
60
  spinDuration?: number;
61
+ friction?: number; // Note: friction prop is declared but not used in original logic
33
62
  enabled?: boolean;
63
+
64
+ // Events
34
65
  onSpinStart?: () => void;
35
66
  onSpinEnd?: (item: SpinWheelItem) => void;
67
+
68
+ // Styling
36
69
  containerStyle?: ViewStyle;
37
70
  centerStyle?: ViewStyle;
38
71
  spinButtonText?: string;
@@ -40,14 +73,19 @@ interface SpinWheelProps {
40
73
  knobStyle?: ViewStyle;
41
74
  actionButtonStyle?: ViewStyle;
42
75
  actionButtonTextStyle?: TextStyle;
76
+
77
+ // Custom colors
78
+ wheelBorderColor?: string;
43
79
  wheelTextColor?: string;
44
80
  knobColor?: string;
45
- centerComponent?: React.ReactNode;
46
- winner?: SpinWheelItem | null; // Winner to spin to
81
+
82
+ // Custom components
83
+ centerComponent?: React.ReactNode; // Note: centerComponent prop is declared but not used
47
84
  }
48
85
 
49
86
  const SpinWheel: React.FC<SpinWheelProps> = ({
50
87
  items,
88
+ predeterminedWinner, // Destructure new prop
51
89
  size = 300,
52
90
  spinDuration = 5000,
53
91
  enabled = true,
@@ -61,52 +99,72 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
61
99
  knobColor = "#D81E5B",
62
100
  actionButtonStyle,
63
101
  actionButtonTextStyle,
102
+ wheelBorderColor,
64
103
  wheelTextColor = "#FFFFFF",
65
- winner,
66
104
  }) => {
67
105
  const wheelItems = items.length > 0 ? items : [];
106
+
68
107
  const [spinning, setSpinning] = useState(false);
69
- const [internalWinner, setInternalWinner] = useState<SpinWheelItem | null>(null);
108
+ const [_, setWinner] = useState<SpinWheelItem | null>(null);
70
109
  const rotateValue = useRef(new Animated.Value(0)).current;
110
+
111
+ // Track rotation manually for calculations
71
112
  const rotationRef = useRef(0);
72
113
 
114
+ // Update tracked rotation when animation completes
73
115
  useEffect(() => {
74
116
  const listener = rotateValue.addListener(({ value }) => {
75
117
  rotationRef.current = value;
76
118
  });
119
+
77
120
  return () => {
78
121
  rotateValue.removeListener(listener);
79
122
  };
80
123
  }, [rotateValue]);
81
124
 
82
- const anglePerItem = 360 / wheelItems.length;
125
+ // Calculate angle for each segment
126
+ const anglePerItem =
127
+ wheelItems.length > 0 ? 360 / wheelItems.length : 0;
83
128
 
84
- // Generate wheel paths and text positions
129
+ // Create wheel segments
85
130
  const generateWheelPaths = () => {
131
+ if (wheelItems.length === 0) return [];
86
132
  return wheelItems.map((item, index) => {
87
133
  const startAngle = index * anglePerItem;
88
134
  const endAngle = (index + 1) * anglePerItem;
135
+
89
136
  const startRad = (startAngle * Math.PI) / 180;
90
137
  const endRad = (endAngle * Math.PI) / 180;
138
+
91
139
  const x1 = size / 2 + (size / 2) * Math.cos(startRad);
92
140
  const y1 = size / 2 + (size / 2) * Math.sin(startRad);
93
141
  const x2 = size / 2 + (size / 2) * Math.cos(endRad);
94
142
  const y2 = size / 2 + (size / 2) * Math.sin(endRad);
143
+
95
144
  const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
145
+
96
146
  const pathData = [
97
147
  `M ${size / 2} ${size / 2}`,
98
148
  `L ${x1} ${y1}`,
99
149
  `A ${size / 2} ${size / 2} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
100
150
  "Z",
101
151
  ].join(" ");
152
+
102
153
  const midRad = ((startAngle + endAngle) / 2) * (Math.PI / 180);
103
154
  const textX = size / 2 + size * 0.32 * Math.cos(midRad);
104
155
  const textY = size / 2 + size * 0.32 * Math.sin(midRad);
156
+
157
+ // decorationX and decorationY are calculated but not used in the provided JSX
158
+ // const decorationX = size / 2 + size * 0.43 * Math.cos(midRad);
159
+ // const decorationY = size / 2 + size * 0.43 * Math.sin(midRad);
160
+
105
161
  return {
106
162
  path: pathData,
107
163
  item,
108
164
  textX,
109
165
  textY,
166
+ // decorationX,
167
+ // decorationY,
110
168
  angle: (startAngle + endAngle) / 2,
111
169
  };
112
170
  });
@@ -114,56 +172,82 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
114
172
 
115
173
  const wheelPaths = generateWheelPaths();
116
174
 
117
- // Helper: get index of a SpinWheelItem
118
- const getItemIndex = (target: SpinWheelItem) =>
119
- wheelItems.findIndex((item) => item.id === target.id);
175
+ // Handle spin button press
176
+ const handleSpin = () => {
177
+ if (spinning || !enabled || wheelItems.length === 0) return;
120
178
 
121
- // Helper: spin to a given index
122
- const spinToIndex = (targetIndex: number, callbackItem: SpinWheelItem) => {
123
179
  setSpinning(true);
124
180
  onSpinStart?.();
125
181
 
126
- // The pointer is at 270deg (top), so we want the winner to land there.
127
- // The center of the segment is at: (index + 0.5) * anglePerItem
128
- const winnerAngle =
129
- 360 - ((targetIndex + 0.5) * anglePerItem - 270);
182
+ let targetRotation = 0;
183
+ let winnerTargetIndex = -1;
184
+
185
+ if (predeterminedWinner) {
186
+ const winnerId =
187
+ typeof predeterminedWinner === "object"
188
+ ? predeterminedWinner.id
189
+ : predeterminedWinner;
190
+ winnerTargetIndex = wheelItems.findIndex((item) => item.id === winnerId);
191
+ }
192
+
193
+ if (winnerTargetIndex !== -1) {
194
+ const targetSegmentCenterAngle =
195
+ (winnerTargetIndex + 0.5) * anglePerItem;
196
+
197
+ const targetNormalizedAngle =
198
+ (270 - targetSegmentCenterAngle + 360) % 360;
199
+
200
+ const currentAbsoluteAngle = rotationRef.current * 360;
201
+ const currentNormalizedAngle = (currentAbsoluteAngle % 360 + 360) % 360;
202
+
203
+ // Additional angle needed to reach the targetNormalizedAngle from currentNormalizedAngle
204
+ const angleOffset =
205
+ (targetNormalizedAngle - currentNormalizedAngle + 360) % 360;
206
+
207
+ // Number of full spins (e.g., 3 to 5)
208
+ const numberOfFullSpins = 3 + Math.floor(Math.random() * 3); // 3, 4, or 5 spins
209
+ targetRotation = numberOfFullSpins * 360 + angleOffset;
210
+
211
+ // Ensure minimum rotation if already aligned (e.g. if angleOffset is 0)
212
+ if (targetRotation < 360) {
213
+ targetRotation += 360 * (3 + Math.floor(Math.random() * 3));
214
+ }
130
215
 
131
- // Add extra spins for effect
132
- const extraSpins = 3;
133
- const currentRotation = rotationRef.current * 360;
134
- const targetRotation = currentRotation + extraSpins * 360 + winnerAngle;
216
+ } else {
217
+ const randomSpins = 3 + Math.random() * 2;
218
+ const randomAngle = Math.random() * 360;
219
+ targetRotation = 360 * randomSpins + randomAngle;
220
+ }
135
221
 
136
222
  Animated.timing(rotateValue, {
137
- toValue: targetRotation / 360,
223
+ toValue: rotationRef.current + targetRotation / 360,
138
224
  duration: spinDuration,
139
225
  easing: Easing.out(Easing.cubic),
140
226
  useNativeDriver: true,
141
- }).start(() => {
142
- setSpinning(false);
143
- setInternalWinner(callbackItem);
144
- onSpinEnd?.(callbackItem);
145
- });
227
+ }).start(() => handleSpinEnd());
146
228
  };
147
229
 
148
- // If winner prop changes, spin to it
149
- useEffect(() => {
150
- if (!winner || spinning) return;
151
- const winnerIndex = getItemIndex(winner);
152
- if (winnerIndex === -1) return;
153
- spinToIndex(winnerIndex, winner);
154
- // eslint-disable-next-line react-hooks/exhaustive-deps
155
- }, [winner]);
156
-
157
- // Handle random spin (if no winner is set)
158
- const handleSpin = () => {
159
- if (spinning || !enabled) return;
160
- // If winner prop is set, ignore button (or you can allow override)
161
- if (winner) return;
162
-
163
- // Pick a random winner
164
- const randomIndex = Math.floor(Math.random() * wheelItems.length);
165
- const randomWinner = wheelItems[randomIndex];
166
- spinToIndex(randomIndex, randomWinner);
230
+ // Handle spin completion
231
+ const handleSpinEnd = () => {
232
+ setSpinning(false);
233
+
234
+ const normalizedAngle = (rotationRef.current * 360) % 360;
235
+ const winningIndex = Math.floor(
236
+ ((normalizedAngle - 270) % 360) / anglePerItem
237
+ );
238
+ const adjustedIndex =
239
+ (wheelItems.length - 1 - winningIndex) % wheelItems.length;
240
+
241
+ const finalWinnerIndex =
242
+ adjustedIndex >= 0 ? adjustedIndex : wheelItems.length + adjustedIndex;
243
+
244
+ if (finalWinnerIndex >= 0 && finalWinnerIndex < wheelItems.length) {
245
+ const winningItem = wheelItems[finalWinnerIndex];
246
+ setWinner(winningItem);
247
+ onSpinEnd?.(winningItem);
248
+ } else if (wheelItems.length > 0) {
249
+ onSpinEnd?.(wheelItems[0]);
250
+ }
167
251
  };
168
252
 
169
253
  // Animation interpolation for rotation
@@ -190,32 +274,38 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
190
274
  <Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
191
275
  <G>
192
276
  {wheelPaths.map(
193
- ({ path, item, textX, textY, angle }, index) => (
194
- <React.Fragment key={item.id}>
195
- <Path
196
- d={path}
197
- fill={
198
- !item.color
199
- ? colors[index % colors.length]
200
- : item.color
201
- }
202
- stroke="#000000"
203
- strokeWidth={1}
204
- />
205
- <SvgText
206
- x={textX}
207
- y={textY}
208
- fill={item.textColor || wheelTextColor}
209
- fontSize={wheelTextStyle?.fontSize || 14}
210
- fontWeight={wheelTextStyle?.fontWeight || "bold"}
211
- textAnchor="middle"
212
- alignmentBaseline="central"
213
- transform={`rotate(${angle + 180}, ${textX}, ${textY})`}
214
- >
215
- {item.label}
216
- </SvgText>
217
- </React.Fragment>
218
- )
277
+ ({ path, item, textX, textY, angle }, index) => {
278
+ return (
279
+ <React.Fragment key={item.id}>
280
+ <Path
281
+ d={path}
282
+ fill={
283
+ item.color == "" || item.color == null
284
+ ? colors[
285
+ Math.floor(Math.random() * colors.length)
286
+ ]
287
+ : item.color
288
+ }
289
+ stroke="#000000"
290
+ strokeWidth={1}
291
+ />
292
+ <SvgText
293
+ x={textX}
294
+ y={textY}
295
+ fill={item.textColor || wheelTextColor}
296
+ fontSize={wheelTextStyle?.fontSize || 14}
297
+ fontWeight={
298
+ (wheelTextStyle?.fontWeight as any) || "bold"
299
+ }
300
+ textAnchor="middle"
301
+ alignmentBaseline="central"
302
+ transform={`rotate(${angle + 180}, ${textX}, ${textY} )`}
303
+ >
304
+ {item.label}
305
+ </SvgText>
306
+ </React.Fragment>
307
+ );
308
+ }
219
309
  )}
220
310
  </G>
221
311
  </Svg>
@@ -232,13 +322,11 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
232
322
  { translateX: -size / 10 },
233
323
  { translateY: -size / 10 },
234
324
  ],
235
- borderRadius: size / 5,
325
+ borderRadius: size / 5, // Should be (size / 5) / 2 for a circle
236
326
  },
237
327
  centerStyle,
238
328
  ]}
239
- >
240
- {/** Optional: custom center component */}
241
- </View>
329
+ />
242
330
 
243
331
  {/* The pointer is a triangle on top */}
244
332
  <View style={styles.pointerPosition}>
@@ -264,7 +352,7 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
264
352
  >
265
353
  <TouchableOpacity
266
354
  onPress={handleSpin}
267
- disabled={spinning || !enabled || !!winner}
355
+ disabled={spinning || !enabled || wheelItems.length === 0}
268
356
  style={[styles.actionButton, actionButtonStyle]}
269
357
  >
270
358
  <Text style={[styles.actionButtonText, actionButtonTextStyle]}>
@@ -282,7 +370,7 @@ const styles = StyleSheet.create({
282
370
  alignItems: "center",
283
371
  justifyContent: "center",
284
372
  marginTop: 20,
285
- marginBottom: 70,
373
+ marginBottom: 70, // Space for the button
286
374
  },
287
375
  wheelContainer: {
288
376
  overflow: "hidden",
@@ -310,10 +398,9 @@ const styles = StyleSheet.create({
310
398
  borderStyle: "solid",
311
399
  borderLeftWidth: 10,
312
400
  borderRightWidth: 10,
313
- borderBottomWidth: 15,
401
+ borderBottomWidth: 15,
314
402
  borderLeftColor: "transparent",
315
403
  borderRightColor: "transparent",
316
- borderBottomColor: "#D81E5B",
317
404
  },
318
405
  actionButton: {
319
406
  paddingHorizontal: 30,
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- // import { registerRootComponent } from 'expo';
2
- // import "react-native-reanimated";
1
+ import { registerRootComponent } from 'expo';
2
+ import "react-native-reanimated";
3
3
 
4
4
 
5
- // import App from "./app";
5
+ import App from "./app";
6
6
 
7
- // registerRootComponent(App);
7
+ registerRootComponent(App);
8
8
 
9
9
  export * from "./theme"
10
10
  export * from "./components";