react-spin-prize 2.0.0 → 2.2.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.
- package/README.md +10 -0
- package/dist/SpinnerWheel.d.ts.map +1 -1
- package/dist/SpinnerWheel.js +61 -25
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,6 +78,7 @@ function App() {
|
|
|
78
78
|
| `disabled` | `boolean` | false | Disable the spin button |
|
|
79
79
|
| `winningIndex` | `number` | - | Force a specific winner (for testing) |
|
|
80
80
|
| `autoSpinTrigger` | `string \| number \| boolean \| null` | - | Change this value to trigger a programmatic spin |
|
|
81
|
+
| `textLayout` | `'radial' \| 'horizontal'` | 'horizontal' | Text layout style: 'radial' for rotating text, 'horizontal' for edge-to-center text |
|
|
81
82
|
|
|
82
83
|
## SpinnerWheelItem
|
|
83
84
|
|
|
@@ -125,6 +126,15 @@ interface SpinnerWheelItem {
|
|
|
125
126
|
console.log('Winner:', item);
|
|
126
127
|
}}
|
|
127
128
|
/>
|
|
129
|
+
|
|
130
|
+
// Horizontal text layout (edge-to-center)
|
|
131
|
+
<SpinnerWheel
|
|
132
|
+
items={items}
|
|
133
|
+
textLayout="horizontal"
|
|
134
|
+
onSpinComplete={(item) => {
|
|
135
|
+
console.log('Winner:', item);
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
128
138
|
```
|
|
129
139
|
|
|
130
140
|
## License
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpinnerWheel.d.ts","sourceRoot":"","sources":["../src/SpinnerWheel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAQjD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"SpinnerWheel.d.ts","sourceRoot":"","sources":["../src/SpinnerWheel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAQjD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAyWpD,CAAC"}
|
package/dist/SpinnerWheel.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
3
3
|
const DEFAULT_COLORS = [
|
|
4
4
|
"#FF6B6B", "#4ECDC4", "#45B7D1", "#FFA07A",
|
|
5
5
|
"#98D8C8", "#F7DC6F", "#BB8FCE", "#85C1E2",
|
|
6
6
|
"#F8B739", "#52B788", "#E76F51", "#2A9D8F",
|
|
7
7
|
];
|
|
8
|
-
export const SpinnerWheel = ({ items, onSpinComplete, onButtonClick, spinning: externalSpinning, duration = 5000, size = 500, fontSize = 16, borderWidth = 8, borderColor = "#333", buttonText = "SPIN", buttonColor = "#333", buttonTextColor = "#fff", buttonIcon, buttonSize, buttonBorderColor = "#333", buttonBorderWidth = 4, disabled = false, winningIndex, autoSpinTrigger, }) => {
|
|
8
|
+
export const SpinnerWheel = ({ items, onSpinComplete, onButtonClick, spinning: externalSpinning, duration = 5000, size = 500, fontSize = 16, borderWidth = 8, borderColor = "#333", buttonText = "SPIN", buttonColor = "#333", buttonTextColor = "#fff", buttonIcon, buttonSize, buttonFontSize, buttonBorderColor = "#333", buttonBorderWidth = 4, disabled = false, winningIndex, autoSpinTrigger, textLayout = "horizontal", }) => {
|
|
9
9
|
const [rotation, setRotation] = useState(0);
|
|
10
10
|
const [spinning, setSpinning] = useState(false);
|
|
11
11
|
const animationRef = useRef();
|
|
@@ -15,7 +15,7 @@ export const SpinnerWheel = ({ items, onSpinComplete, onButtonClick, spinning: e
|
|
|
15
15
|
const centerX = radius;
|
|
16
16
|
const centerY = radius;
|
|
17
17
|
const wheelRadius = radius - borderWidth;
|
|
18
|
-
const buttonRadius = buttonSize !== undefined ? buttonSize : radius * 0.
|
|
18
|
+
const buttonRadius = buttonSize !== undefined ? buttonSize : radius * 0.15;
|
|
19
19
|
const segmentAngle = 360 / items.length;
|
|
20
20
|
// Clean up animation on unmount
|
|
21
21
|
useEffect(() => {
|
|
@@ -100,12 +100,12 @@ export const SpinnerWheel = ({ items, onSpinComplete, onButtonClick, spinning: e
|
|
|
100
100
|
const getTextColor = (index) => {
|
|
101
101
|
return items[index].textColor || "#fff";
|
|
102
102
|
};
|
|
103
|
-
// Dynamic font size for many items
|
|
103
|
+
// Dynamic font size for many items - made smaller
|
|
104
104
|
const dynamicFontSize = items.length > 20
|
|
105
|
-
? Math.max(
|
|
105
|
+
? Math.max(6, fontSize * 0.5 - Math.floor((items.length - 20) / 10) * 2)
|
|
106
106
|
: items.length > 12
|
|
107
|
-
? Math.max(
|
|
108
|
-
: fontSize;
|
|
107
|
+
? Math.max(8, fontSize * 0.6)
|
|
108
|
+
: fontSize * 0.7;
|
|
109
109
|
const renderSegments = () => {
|
|
110
110
|
return items.map((item, index) => {
|
|
111
111
|
// Draw segments starting from top (-90°) going clockwise
|
|
@@ -122,25 +122,61 @@ export const SpinnerWheel = ({ items, onSpinComplete, onButtonClick, spinning: e
|
|
|
122
122
|
`A ${wheelRadius} ${wheelRadius} 0 0 1 ${x2} ${y2}`,
|
|
123
123
|
"Z",
|
|
124
124
|
].join(" ");
|
|
125
|
-
// Text position
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
// Text position and rendering based on layout
|
|
126
|
+
let textElement;
|
|
127
|
+
if (textLayout === "horizontal") {
|
|
128
|
+
// Horizontal layout: text starts from edge towards center
|
|
129
|
+
const outerTextRadius = wheelRadius * 0.85;
|
|
130
|
+
const innerTextRadius = wheelRadius * 0.25;
|
|
131
|
+
// Calculate start position (at the edge)
|
|
132
|
+
const startX = centerX + outerTextRadius * Math.cos(midAngle);
|
|
133
|
+
const startY = centerY + outerTextRadius * Math.sin(midAngle);
|
|
134
|
+
// Calculate end position (towards center)
|
|
135
|
+
const endX = centerX + innerTextRadius * Math.cos(midAngle);
|
|
136
|
+
const endY = centerY + innerTextRadius * Math.sin(midAngle);
|
|
137
|
+
// Text path for horizontal text
|
|
138
|
+
const textPathId = `textPath-${item.id}`;
|
|
139
|
+
// Truncate long labels - more aggressive truncation
|
|
140
|
+
let displayLabel = item.label;
|
|
141
|
+
if (items.length > 30 && displayLabel.length > 6) {
|
|
142
|
+
displayLabel = displayLabel.substring(0, 5) + "...";
|
|
143
|
+
}
|
|
144
|
+
else if (items.length > 20 && displayLabel.length > 8) {
|
|
145
|
+
displayLabel = displayLabel.substring(0, 7) + "...";
|
|
146
|
+
}
|
|
147
|
+
else if (items.length > 12 && displayLabel.length > 10) {
|
|
148
|
+
displayLabel = displayLabel.substring(0, 9) + "...";
|
|
149
|
+
}
|
|
150
|
+
else if (displayLabel.length > 12) {
|
|
151
|
+
displayLabel = displayLabel.substring(0, 11) + "...";
|
|
152
|
+
}
|
|
153
|
+
textElement = (_jsxs(_Fragment, { children: [_jsx("defs", { children: _jsx("path", { id: textPathId, d: `M ${startX} ${startY} L ${endX} ${endY}` }) }), _jsx("text", { fill: getTextColor(index), fontSize: dynamicFontSize, fontWeight: "bold", style: { userSelect: "none", pointerEvents: "none" }, children: _jsx("textPath", { href: `#${textPathId}`, startOffset: "50%", textAnchor: "middle", children: displayLabel }) })] }));
|
|
139
154
|
}
|
|
140
|
-
else
|
|
141
|
-
|
|
155
|
+
else {
|
|
156
|
+
// Radial layout (default): rotating text
|
|
157
|
+
const textRadius = items.length > 30 ? wheelRadius * 0.65
|
|
158
|
+
: items.length > 20 ? wheelRadius * 0.68
|
|
159
|
+
: wheelRadius * 0.7;
|
|
160
|
+
const textX = centerX + textRadius * Math.cos(midAngle);
|
|
161
|
+
const textY = centerY + textRadius * Math.sin(midAngle);
|
|
162
|
+
const textAngle = (midAngle * 180) / Math.PI + 90;
|
|
163
|
+
// Truncate long labels - more aggressive truncation
|
|
164
|
+
let displayLabel = item.label;
|
|
165
|
+
if (items.length > 30 && displayLabel.length > 6) {
|
|
166
|
+
displayLabel = displayLabel.substring(0, 5) + "...";
|
|
167
|
+
}
|
|
168
|
+
else if (items.length > 20 && displayLabel.length > 8) {
|
|
169
|
+
displayLabel = displayLabel.substring(0, 7) + "...";
|
|
170
|
+
}
|
|
171
|
+
else if (items.length > 12 && displayLabel.length > 10) {
|
|
172
|
+
displayLabel = displayLabel.substring(0, 9) + "...";
|
|
173
|
+
}
|
|
174
|
+
else if (displayLabel.length > 12) {
|
|
175
|
+
displayLabel = displayLabel.substring(0, 11) + "...";
|
|
176
|
+
}
|
|
177
|
+
textElement = (_jsx("text", { x: textX, y: textY, fill: getTextColor(index), fontSize: dynamicFontSize, fontWeight: "bold", textAnchor: "middle", dominantBaseline: "middle", transform: `rotate(${textAngle} ${textX} ${textY})`, style: { userSelect: "none", pointerEvents: "none" }, children: displayLabel }));
|
|
142
178
|
}
|
|
143
|
-
return (_jsxs("g", { children: [_jsx("path", { d: pathData, fill: getColor(index), stroke: borderColor, strokeWidth: 1 }),
|
|
179
|
+
return (_jsxs("g", { children: [_jsx("path", { d: pathData, fill: getColor(index), stroke: borderColor, strokeWidth: 1 }), textElement] }, item.id));
|
|
144
180
|
});
|
|
145
181
|
};
|
|
146
182
|
return (_jsxs("div", { style: { position: "relative", width: size, height: size, margin: "0 auto" }, children: [_jsxs("svg", { width: size, height: size, style: {
|
|
@@ -164,7 +200,7 @@ export const SpinnerWheel = ({ items, onSpinComplete, onButtonClick, spinning: e
|
|
|
164
200
|
backgroundColor: buttonColor,
|
|
165
201
|
color: buttonTextColor,
|
|
166
202
|
border: "none",
|
|
167
|
-
fontSize: buttonIcon ? "inherit" : fontSize *
|
|
203
|
+
fontSize: buttonIcon ? "inherit" : (buttonFontSize !== undefined ? buttonFontSize : fontSize * 0.6),
|
|
168
204
|
fontWeight: buttonIcon ? "normal" : "bold",
|
|
169
205
|
cursor: disabled || isSpinning ? "not-allowed" : "pointer",
|
|
170
206
|
opacity: disabled || isSpinning ? 0.6 : 1,
|
package/dist/types.d.ts
CHANGED
|
@@ -19,10 +19,12 @@ export interface SpinnerWheelProps {
|
|
|
19
19
|
buttonTextColor?: string;
|
|
20
20
|
buttonIcon?: React.ReactNode;
|
|
21
21
|
buttonSize?: number;
|
|
22
|
+
buttonFontSize?: number;
|
|
22
23
|
buttonBorderColor?: string;
|
|
23
24
|
buttonBorderWidth?: number;
|
|
24
25
|
disabled?: boolean;
|
|
25
26
|
winningIndex?: number;
|
|
26
27
|
autoSpinTrigger?: string | number | boolean | null;
|
|
28
|
+
textLayout?: "radial" | "horizontal";
|
|
27
29
|
}
|
|
28
30
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,aAAa,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,aAAa,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACnD,UAAU,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;CACtC"}
|