related-ui-components 1.8.3 → 1.8.5
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.
- package/lib/commonjs/components/RedeemedVoucher/RedeemedVoucherSheet.js +9 -1
- package/lib/commonjs/components/RedeemedVoucher/RedeemedVoucherSheet.js.map +1 -1
- package/lib/commonjs/components/Wheel/Wheel.js +185 -175
- package/lib/commonjs/components/Wheel/Wheel.js.map +1 -1
- package/lib/module/components/RedeemedVoucher/RedeemedVoucherSheet.js +9 -1
- package/lib/module/components/RedeemedVoucher/RedeemedVoucherSheet.js.map +1 -1
- package/lib/module/components/Wheel/Wheel.js +187 -176
- package/lib/module/components/Wheel/Wheel.js.map +1 -1
- package/lib/typescript/commonjs/components/RedeemedVoucher/RedeemedVoucherSheet.d.ts.map +1 -1
- package/lib/typescript/commonjs/components/Wheel/Wheel.d.ts +1 -2
- package/lib/typescript/commonjs/components/Wheel/Wheel.d.ts.map +1 -1
- package/lib/typescript/module/components/RedeemedVoucher/RedeemedVoucherSheet.d.ts.map +1 -1
- package/lib/typescript/module/components/Wheel/Wheel.d.ts +1 -2
- package/lib/typescript/module/components/Wheel/Wheel.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/RedeemedVoucher/RedeemedVoucherSheet.tsx +12 -1
- package/src/components/Wheel/Wheel.tsx +239 -205
|
@@ -165,6 +165,14 @@ const RedeemedVoucherSheet: React.FC<Props> = (props) => {
|
|
|
165
165
|
onCopyCode(code);
|
|
166
166
|
}
|
|
167
167
|
};
|
|
168
|
+
const closeBottomSheet = useCallback(
|
|
169
|
+
() => {
|
|
170
|
+
bottomSheetRef.current?.close();
|
|
171
|
+
},
|
|
172
|
+
[
|
|
173
|
+
/* onClose */
|
|
174
|
+
]
|
|
175
|
+
);
|
|
168
176
|
|
|
169
177
|
// Custom backdrop component
|
|
170
178
|
const renderBackdrop = useCallback(
|
|
@@ -346,7 +354,10 @@ const RedeemedVoucherSheet: React.FC<Props> = (props) => {
|
|
|
346
354
|
{onMyVouchersButtonPress && (
|
|
347
355
|
<TouchableOpacity
|
|
348
356
|
style={styles.vouchersButton}
|
|
349
|
-
onPress={
|
|
357
|
+
onPress={() => {
|
|
358
|
+
closeBottomSheet();
|
|
359
|
+
onMyVouchersButtonPress()
|
|
360
|
+
}}
|
|
350
361
|
>
|
|
351
362
|
<LinearGradient
|
|
352
363
|
colors={primaryButtonColors as any}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo, // Added useMemo for wheelPaths
|
|
6
|
+
} from "react";
|
|
2
7
|
import {
|
|
3
8
|
View,
|
|
4
9
|
StyleSheet,
|
|
@@ -15,56 +20,46 @@ export interface SpinWheelItem {
|
|
|
15
20
|
id: string | number;
|
|
16
21
|
label: string;
|
|
17
22
|
value?: any;
|
|
18
|
-
color: string;
|
|
23
|
+
color: string; // Expect color to be provided, or handle default more explicitly
|
|
19
24
|
textColor?: string;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
27
|
+
// Default random colors (can be used if item.color is not provided)
|
|
28
|
+
const defaultColors = [
|
|
29
|
+
"#FF0000",
|
|
30
|
+
"#FFA500",
|
|
31
|
+
"#FFFF00",
|
|
32
|
+
"#008000",
|
|
33
|
+
"#0000FF",
|
|
34
|
+
"#800080",
|
|
35
|
+
"#FFC0CB",
|
|
36
|
+
"#00FFFF",
|
|
37
|
+
"#FF00FF",
|
|
38
|
+
"#00FF00",
|
|
39
|
+
"#4B0082",
|
|
40
|
+
"#EE82EE",
|
|
41
|
+
"#40E0D0",
|
|
42
|
+
"#FFD700",
|
|
43
|
+
"#C0C0C0",
|
|
44
|
+
"#FFDAB9",
|
|
45
|
+
"#E6E6FA",
|
|
46
|
+
"#008080",
|
|
47
|
+
"#FF7F50",
|
|
48
|
+
"#DC143C",
|
|
49
|
+
"#87CEEB",
|
|
50
|
+
"#7FFF00",
|
|
51
|
+
"#CCCCFF",
|
|
52
|
+
"#FF6347",
|
|
53
|
+
"#FA8072",
|
|
49
54
|
];
|
|
50
55
|
|
|
51
56
|
interface SpinWheelProps {
|
|
52
|
-
// Data
|
|
53
57
|
items: SpinWheelItem[];
|
|
54
|
-
|
|
55
|
-
// Dimensions
|
|
56
58
|
size?: number;
|
|
57
|
-
|
|
58
|
-
// Behavior
|
|
59
59
|
spinDuration?: number;
|
|
60
|
-
friction?: number;
|
|
61
60
|
enabled?: boolean;
|
|
62
|
-
|
|
63
|
-
// Events
|
|
64
61
|
onSpinStart?: () => void;
|
|
65
62
|
onSpinEnd?: (item: SpinWheelItem) => void;
|
|
66
|
-
|
|
67
|
-
// Styling
|
|
68
63
|
containerStyle?: ViewStyle;
|
|
69
64
|
centerStyle?: ViewStyle;
|
|
70
65
|
spinButtonText?: string;
|
|
@@ -72,14 +67,10 @@ interface SpinWheelProps {
|
|
|
72
67
|
knobStyle?: ViewStyle;
|
|
73
68
|
actionButtonStyle?: ViewStyle;
|
|
74
69
|
actionButtonTextStyle?: TextStyle;
|
|
75
|
-
|
|
76
|
-
// Custom colors
|
|
77
|
-
wheelBorderColor?: string;
|
|
78
70
|
wheelTextColor?: string;
|
|
79
71
|
knobColor?: string;
|
|
80
|
-
|
|
81
|
-
// Custom components
|
|
82
72
|
centerComponent?: React.ReactNode;
|
|
73
|
+
predeterminedWinnerId?: string | number | null; // New prop
|
|
83
74
|
}
|
|
84
75
|
|
|
85
76
|
const SpinWheel: React.FC<SpinWheelProps> = ({
|
|
@@ -98,127 +89,188 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
|
|
|
98
89
|
actionButtonStyle,
|
|
99
90
|
actionButtonTextStyle,
|
|
100
91
|
wheelTextColor = "#FFFFFF",
|
|
92
|
+
centerComponent,
|
|
93
|
+
predeterminedWinnerId = null, // Default to null
|
|
101
94
|
}) => {
|
|
102
|
-
const wheelItems =
|
|
95
|
+
const wheelItems = useMemo(
|
|
96
|
+
() => (items && items.length > 0 ? items : []),
|
|
97
|
+
[items]
|
|
98
|
+
);
|
|
103
99
|
|
|
104
100
|
const [spinning, setSpinning] = useState(false);
|
|
105
|
-
const [
|
|
101
|
+
const [_winnerState, setWinnerState] = useState<SpinWheelItem | null>(null); // Renamed to avoid conflict
|
|
106
102
|
const rotateValue = useRef(new Animated.Value(0)).current;
|
|
103
|
+
const rotationRef = useRef(0); // Tracks cumulative rotation in "number of turns"
|
|
107
104
|
|
|
108
|
-
// Track rotation manually for calculations
|
|
109
|
-
const rotationRef = useRef(0);
|
|
110
|
-
|
|
111
|
-
// Update tracked rotation when animation completes
|
|
112
105
|
useEffect(() => {
|
|
113
|
-
const
|
|
106
|
+
const listenerId = rotateValue.addListener(({ value }) => {
|
|
114
107
|
rotationRef.current = value;
|
|
115
108
|
});
|
|
116
|
-
|
|
117
109
|
return () => {
|
|
118
|
-
rotateValue.removeListener(
|
|
110
|
+
rotateValue.removeListener(listenerId);
|
|
119
111
|
};
|
|
120
112
|
}, [rotateValue]);
|
|
121
113
|
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
const anglePerItem =
|
|
115
|
+
wheelItems.length > 0 ? 360 / wheelItems.length : 0;
|
|
124
116
|
|
|
125
|
-
|
|
126
|
-
|
|
117
|
+
const wheelPaths = useMemo(() => {
|
|
118
|
+
if (wheelItems.length === 0) return [];
|
|
127
119
|
return wheelItems.map((item, index) => {
|
|
128
120
|
const startAngle = index * anglePerItem;
|
|
129
121
|
const endAngle = (index + 1) * anglePerItem;
|
|
130
|
-
|
|
131
|
-
// Calculate path for segment
|
|
132
122
|
const startRad = (startAngle * Math.PI) / 180;
|
|
133
123
|
const endRad = (endAngle * Math.PI) / 180;
|
|
134
|
-
|
|
135
124
|
const x1 = size / 2 + (size / 2) * Math.cos(startRad);
|
|
136
125
|
const y1 = size / 2 + (size / 2) * Math.sin(startRad);
|
|
137
126
|
const x2 = size / 2 + (size / 2) * Math.cos(endRad);
|
|
138
127
|
const y2 = size / 2 + (size / 2) * Math.sin(endRad);
|
|
139
|
-
|
|
140
|
-
const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
|
|
141
|
-
|
|
128
|
+
const largeArcFlag = anglePerItem <= 180 ? "0" : "1";
|
|
142
129
|
const pathData = [
|
|
143
130
|
`M ${size / 2} ${size / 2}`,
|
|
144
131
|
`L ${x1} ${y1}`,
|
|
145
132
|
`A ${size / 2} ${size / 2} 0 ${largeArcFlag} 1 ${x2} ${y2}`,
|
|
146
133
|
"Z",
|
|
147
134
|
].join(" ");
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const midRad = ((startAngle + endAngle) / 2) * (Math.PI / 180);
|
|
135
|
+
const midAngle = startAngle + anglePerItem / 2;
|
|
136
|
+
const midRad = (midAngle * Math.PI) / 180;
|
|
151
137
|
const textX = size / 2 + size * 0.32 * Math.cos(midRad);
|
|
152
138
|
const textY = size / 2 + size * 0.32 * Math.sin(midRad);
|
|
153
|
-
|
|
154
|
-
const decorationX = size / 2 + size * 0.43 * Math.cos(midRad);
|
|
155
|
-
const decorationY = size / 2 + size * 0.43 * Math.sin(midRad);
|
|
156
|
-
|
|
157
139
|
return {
|
|
158
140
|
path: pathData,
|
|
159
141
|
item,
|
|
160
142
|
textX,
|
|
161
143
|
textY,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
angle: (startAngle + endAngle) / 2,
|
|
144
|
+
angle: midAngle,
|
|
145
|
+
color: item.color || defaultColors[index % defaultColors.length],
|
|
165
146
|
};
|
|
166
147
|
});
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const wheelPaths = generateWheelPaths();
|
|
148
|
+
}, [wheelItems, anglePerItem, size]);
|
|
170
149
|
|
|
171
|
-
// Handle spin button press
|
|
172
150
|
const handleSpin = () => {
|
|
173
|
-
if (spinning || !enabled)
|
|
151
|
+
if (spinning || !enabled || wheelItems.length === 0 || anglePerItem === 0) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
174
154
|
|
|
175
155
|
setSpinning(true);
|
|
176
156
|
onSpinStart?.();
|
|
177
157
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
158
|
+
let targetAnimationToValue: number;
|
|
159
|
+
let winnerToAnnounce: SpinWheelItem | null = null;
|
|
160
|
+
|
|
161
|
+
const currentWinnerId = predeterminedWinnerId; // Capture prop value at spin start
|
|
162
|
+
let targetWinnerItem: SpinWheelItem | null = null;
|
|
163
|
+
|
|
164
|
+
if (currentWinnerId != null) {
|
|
165
|
+
const foundItem = wheelItems.find((item) => item.id === currentWinnerId);
|
|
166
|
+
if (foundItem) {
|
|
167
|
+
targetWinnerItem = foundItem;
|
|
168
|
+
} else {
|
|
169
|
+
console.warn(
|
|
170
|
+
`SpinWheel: Predetermined winner with id "${currentWinnerId}" not found. Spinning randomly.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (targetWinnerItem) {
|
|
176
|
+
winnerToAnnounce = targetWinnerItem;
|
|
177
|
+
const winnerIndex = wheelItems.indexOf(targetWinnerItem);
|
|
178
|
+
|
|
179
|
+
// Calculate the middle angle of the target segment within the wheel's coordinate system
|
|
180
|
+
const targetSegmentMidAngle_deg = (winnerIndex + 0.5) * anglePerItem;
|
|
181
|
+
|
|
182
|
+
// The pointer is at 270 degrees (top). We want the targetSegmentMidAngle_deg
|
|
183
|
+
// to align with this pointer.
|
|
184
|
+
// If the wheel rotates by R, a point A on wheel is at (A+R)%360.
|
|
185
|
+
// So, (targetSegmentMidAngle_deg + R) % 360 = 270.
|
|
186
|
+
// R % 360 = (270 - targetSegmentMidAngle_deg + 360*k) % 360.
|
|
187
|
+
// This R % 360 is the target orientation for the wheel's 0-degree mark.
|
|
188
|
+
const targetWheelOrientation_deg =
|
|
189
|
+
(270 - targetSegmentMidAngle_deg + 360 * 10) % 360; // *10 to ensure positive
|
|
190
|
+
|
|
191
|
+
const currentWheelOrientation_deg = (rotationRef.current * 360) % 360;
|
|
192
|
+
|
|
193
|
+
// Additional rotation needed to reach the target orientation (shortest positive path)
|
|
194
|
+
const rotationToTarget_deg =
|
|
195
|
+
(targetWheelOrientation_deg - currentWheelOrientation_deg + 360) % 360;
|
|
196
|
+
|
|
197
|
+
const numFullRotations = 3 + Math.floor(Math.random() * 3); // 3 to 5 full spins
|
|
198
|
+
const totalAdditionalRotation_deg =
|
|
199
|
+
numFullRotations * 360 + rotationToTarget_deg;
|
|
200
|
+
|
|
201
|
+
targetAnimationToValue =
|
|
202
|
+
rotationRef.current + totalAdditionalRotation_deg / 360;
|
|
203
|
+
} else {
|
|
204
|
+
// Fallback to original random spin logic
|
|
205
|
+
const randomSpins = 3 + Math.random() * 2; // 3-5 full rotations
|
|
206
|
+
const randomAngleOffset = Math.random() * 360;
|
|
207
|
+
const totalRandomRotation_deg = 360 * randomSpins + randomAngleOffset;
|
|
208
|
+
targetAnimationToValue =
|
|
209
|
+
rotationRef.current + totalRandomRotation_deg / 360;
|
|
210
|
+
}
|
|
182
211
|
|
|
183
212
|
Animated.timing(rotateValue, {
|
|
184
|
-
toValue:
|
|
213
|
+
toValue: targetAnimationToValue,
|
|
185
214
|
duration: spinDuration,
|
|
186
215
|
easing: Easing.out(Easing.cubic),
|
|
187
216
|
useNativeDriver: true,
|
|
188
|
-
}).start(() =>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
217
|
+
}).start(() => {
|
|
218
|
+
setSpinning(false);
|
|
219
|
+
|
|
220
|
+
if (winnerToAnnounce) {
|
|
221
|
+
setWinnerState(winnerToAnnounce);
|
|
222
|
+
onSpinEnd?.(winnerToAnnounce);
|
|
223
|
+
} else {
|
|
224
|
+
// Calculate winner from random spin based on final rotation
|
|
225
|
+
const finalRotationDegrees = rotationRef.current * 360;
|
|
226
|
+
const pointerFixedAt_deg = 270; // Pointer is at the top
|
|
227
|
+
|
|
228
|
+
// Determine which original angle on the wheel is now under the pointer
|
|
229
|
+
const angleUnderPointer_orig =
|
|
230
|
+
(pointerFixedAt_deg - finalRotationDegrees + 360 * 10) % 360; // *10 for positive
|
|
231
|
+
|
|
232
|
+
let winningSegmentIndex = Math.floor(
|
|
233
|
+
angleUnderPointer_orig / anglePerItem
|
|
234
|
+
);
|
|
235
|
+
// Ensure index is within bounds
|
|
236
|
+
winningSegmentIndex = Math.max(
|
|
237
|
+
0,
|
|
238
|
+
Math.min(wheelItems.length - 1, winningSegmentIndex)
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const calculatedWinner = wheelItems[winningSegmentIndex];
|
|
242
|
+
|
|
243
|
+
if (calculatedWinner) {
|
|
244
|
+
setWinnerState(calculatedWinner);
|
|
245
|
+
onSpinEnd?.(calculatedWinner);
|
|
246
|
+
} else if (wheelItems.length > 0) {
|
|
247
|
+
// Fallback, should not happen if items exist and calculation is correct
|
|
248
|
+
console.error(
|
|
249
|
+
"SpinWheel: Could not determine winner from random spin. Falling back to first item."
|
|
250
|
+
);
|
|
251
|
+
setWinnerState(wheelItems[0]);
|
|
252
|
+
onSpinEnd?.(wheelItems[0]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
210
256
|
};
|
|
211
257
|
|
|
212
|
-
|
|
213
|
-
const rotate = rotateValue.interpolate({
|
|
258
|
+
const rotateStyle = rotateValue.interpolate({
|
|
214
259
|
inputRange: [0, 1],
|
|
215
260
|
outputRange: ["0deg", "360deg"],
|
|
216
261
|
});
|
|
217
262
|
|
|
263
|
+
if (wheelItems.length === 0) {
|
|
264
|
+
return (
|
|
265
|
+
<View style={[styles.container, containerStyle, { height: size }]}>
|
|
266
|
+
<Text>No items to display in the wheel.</Text>
|
|
267
|
+
</View>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
218
271
|
return (
|
|
219
272
|
<View style={[styles.container, containerStyle]}>
|
|
220
|
-
<View style={{ width: size, height: size }}>
|
|
221
|
-
{/* The wheel */}
|
|
273
|
+
<View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
|
|
222
274
|
<Animated.View
|
|
223
275
|
style={[
|
|
224
276
|
styles.wheelContainer,
|
|
@@ -226,93 +278,66 @@ const SpinWheel: React.FC<SpinWheelProps> = ({
|
|
|
226
278
|
width: size,
|
|
227
279
|
height: size,
|
|
228
280
|
borderRadius: size / 2,
|
|
229
|
-
transform: [{ rotate }],
|
|
281
|
+
transform: [{ rotate: rotateStyle }],
|
|
230
282
|
},
|
|
231
283
|
]}
|
|
232
284
|
>
|
|
233
285
|
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
234
286
|
<G>
|
|
235
|
-
{wheelPaths.map(
|
|
236
|
-
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
y={textY}
|
|
253
|
-
fill={item.textColor || wheelTextColor}
|
|
254
|
-
fontSize={wheelTextStyle?.fontSize || 14}
|
|
255
|
-
fontWeight={wheelTextStyle?.fontWeight || "bold"}
|
|
256
|
-
textAnchor="middle"
|
|
257
|
-
alignmentBaseline="central"
|
|
258
|
-
transform={`rotate(${angle + 180}, ${textX}, ${textY} )`}
|
|
259
|
-
>
|
|
260
|
-
{item.label}
|
|
261
|
-
</SvgText>
|
|
262
|
-
</React.Fragment>
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
)}
|
|
287
|
+
{wheelPaths.map(({ path, item, textX, textY, angle, color }) => (
|
|
288
|
+
<React.Fragment key={item.id}>
|
|
289
|
+
<Path d={path} fill={color} stroke="#000000" strokeWidth={0.5} />
|
|
290
|
+
<SvgText
|
|
291
|
+
x={textX}
|
|
292
|
+
y={textY}
|
|
293
|
+
fill={item.textColor || wheelTextColor}
|
|
294
|
+
fontSize={wheelTextStyle?.fontSize || Math.max(8, size / 25)} // Dynamic font size
|
|
295
|
+
fontWeight={wheelTextStyle?.fontWeight || "bold"}
|
|
296
|
+
textAnchor="middle"
|
|
297
|
+
alignmentBaseline="central"
|
|
298
|
+
transform={`rotate(${angle + 90}, ${textX}, ${textY} )`} // Adjusted rotation for readability
|
|
299
|
+
>
|
|
300
|
+
{item.label}
|
|
301
|
+
</SvgText>
|
|
302
|
+
</React.Fragment>
|
|
303
|
+
))}
|
|
266
304
|
</G>
|
|
267
305
|
</Svg>
|
|
268
306
|
</Animated.View>
|
|
269
307
|
|
|
270
|
-
{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
{
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
{
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
{/* Action Button */}
|
|
293
|
-
<View
|
|
294
|
-
style={{
|
|
295
|
-
position: "absolute",
|
|
296
|
-
width: "100%",
|
|
297
|
-
alignItems: "center",
|
|
298
|
-
justifyContent: "center",
|
|
299
|
-
bottom: -70,
|
|
300
|
-
zIndex: 2,
|
|
301
|
-
}}
|
|
302
|
-
>
|
|
303
|
-
<TouchableOpacity
|
|
304
|
-
onPress={handleSpin}
|
|
305
|
-
disabled={spinning || !enabled}
|
|
306
|
-
style={[styles.actionButton, actionButtonStyle]}
|
|
307
|
-
>
|
|
308
|
-
<Text
|
|
309
|
-
style={[styles.actionButtonText, actionButtonTextStyle]}
|
|
310
|
-
>
|
|
311
|
-
{spinButtonText}
|
|
312
|
-
</Text>
|
|
313
|
-
</TouchableOpacity>
|
|
308
|
+
{centerComponent ? (
|
|
309
|
+
<View style={styles.centerComponentWrapper}>{centerComponent}</View>
|
|
310
|
+
) : (
|
|
311
|
+
<View
|
|
312
|
+
style={[
|
|
313
|
+
styles.wheelCenter,
|
|
314
|
+
{
|
|
315
|
+
width: size / 5,
|
|
316
|
+
height: size / 5,
|
|
317
|
+
borderRadius: size / 10, // Should be half of width/height
|
|
318
|
+
// Centering is now handled by absolute positioning from parent
|
|
319
|
+
},
|
|
320
|
+
centerStyle,
|
|
321
|
+
]}
|
|
322
|
+
/>
|
|
323
|
+
)}
|
|
324
|
+
|
|
325
|
+
<View style={styles.pointerContainer}>
|
|
326
|
+
<View
|
|
327
|
+
style={[styles.pointer, { borderBottomColor: knobColor }, knobStyle]}
|
|
328
|
+
/>
|
|
314
329
|
</View>
|
|
315
330
|
</View>
|
|
331
|
+
|
|
332
|
+
<TouchableOpacity
|
|
333
|
+
onPress={handleSpin}
|
|
334
|
+
disabled={spinning || !enabled}
|
|
335
|
+
style={[styles.actionButton, actionButtonStyle]}
|
|
336
|
+
>
|
|
337
|
+
<Text style={[styles.actionButtonText, actionButtonTextStyle]}>
|
|
338
|
+
{spinButtonText}
|
|
339
|
+
</Text>
|
|
340
|
+
</TouchableOpacity>
|
|
316
341
|
</View>
|
|
317
342
|
);
|
|
318
343
|
};
|
|
@@ -321,52 +346,61 @@ const styles = StyleSheet.create({
|
|
|
321
346
|
container: {
|
|
322
347
|
alignItems: "center",
|
|
323
348
|
justifyContent: "center",
|
|
324
|
-
|
|
325
|
-
marginBottom: 70, // Space for the button
|
|
349
|
+
paddingVertical: 20, // Added padding
|
|
326
350
|
},
|
|
327
351
|
wheelContainer: {
|
|
328
352
|
overflow: "hidden",
|
|
329
|
-
backgroundColor: "transparent",
|
|
353
|
+
backgroundColor: "transparent", // Ensure SVG is visible
|
|
354
|
+
alignItems: "center",
|
|
355
|
+
justifyContent: "center",
|
|
330
356
|
},
|
|
331
|
-
|
|
357
|
+
centerComponentWrapper: { // For custom center component
|
|
358
|
+
position: 'absolute',
|
|
359
|
+
zIndex: 2,
|
|
360
|
+
},
|
|
361
|
+
wheelCenter: { // Default center circle
|
|
332
362
|
position: "absolute",
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
borderColor: "#333333",
|
|
338
|
-
zIndex: 1,
|
|
363
|
+
backgroundColor: "#333333", // Darker center
|
|
364
|
+
borderWidth: 2,
|
|
365
|
+
borderColor: "#FFFFFF", // White border for center
|
|
366
|
+
zIndex: 2, // Above wheel paths, below pointer
|
|
339
367
|
},
|
|
340
|
-
|
|
368
|
+
pointerContainer: { // Position the pointer correctly at the top center
|
|
341
369
|
position: "absolute",
|
|
370
|
+
top: -5, // Adjust to make pointer sit nicely on the edge
|
|
342
371
|
left: "50%",
|
|
343
|
-
transform: [{ translateX: -10 },
|
|
344
|
-
zIndex:
|
|
372
|
+
transform: [{ translateX: -10 }], // Half of pointer base width
|
|
373
|
+
zIndex: 3, // Above everything else on the wheel
|
|
345
374
|
},
|
|
346
375
|
pointer: {
|
|
347
376
|
width: 0,
|
|
348
377
|
height: 0,
|
|
349
378
|
backgroundColor: "transparent",
|
|
350
379
|
borderStyle: "solid",
|
|
351
|
-
borderLeftWidth: 10,
|
|
352
|
-
borderRightWidth: 10,
|
|
353
|
-
borderBottomWidth:
|
|
380
|
+
borderLeftWidth: 10, // Base of triangle
|
|
381
|
+
borderRightWidth: 10, // Base of triangle
|
|
382
|
+
borderBottomWidth: 20, // Height of triangle
|
|
354
383
|
borderLeftColor: "transparent",
|
|
355
384
|
borderRightColor: "transparent",
|
|
356
|
-
borderBottomColor
|
|
385
|
+
// borderBottomColor is set by knobColor prop
|
|
357
386
|
},
|
|
358
387
|
actionButton: {
|
|
388
|
+
marginTop: 30, // Space between wheel and button
|
|
359
389
|
paddingHorizontal: 30,
|
|
360
390
|
paddingVertical: 12,
|
|
361
391
|
borderRadius: 25,
|
|
392
|
+
backgroundColor: "#4CAF50", // Example button color
|
|
362
393
|
shadowColor: "#000",
|
|
363
|
-
|
|
364
|
-
|
|
394
|
+
shadowOffset: { width: 0, height: 2 },
|
|
395
|
+
shadowOpacity: 0.25,
|
|
396
|
+
shadowRadius: 3.84,
|
|
397
|
+
elevation: 5,
|
|
365
398
|
},
|
|
366
399
|
actionButtonText: {
|
|
400
|
+
color: "#FFFFFF",
|
|
367
401
|
fontWeight: "bold",
|
|
368
402
|
fontSize: 16,
|
|
369
|
-
}
|
|
403
|
+
},
|
|
370
404
|
});
|
|
371
405
|
|
|
372
406
|
export default SpinWheel;
|