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 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,CAwSpD,CAAC"}
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"}
@@ -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.25;
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(8, fontSize - Math.floor((items.length - 20) / 10) * 2)
105
+ ? Math.max(6, fontSize * 0.5 - Math.floor((items.length - 20) / 10) * 2)
106
106
  : items.length > 12
107
- ? Math.max(10, fontSize - 2)
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
- const textRadius = items.length > 30 ? wheelRadius * 0.65
127
- : items.length > 20 ? wheelRadius * 0.68
128
- : wheelRadius * 0.7;
129
- const textX = centerX + textRadius * Math.cos(midAngle);
130
- const textY = centerY + textRadius * Math.sin(midAngle);
131
- const textAngle = (midAngle * 180) / Math.PI + 90;
132
- // Truncate long labels
133
- let displayLabel = item.label;
134
- if (items.length > 30 && displayLabel.length > 8) {
135
- displayLabel = displayLabel.substring(0, 7) + "...";
136
- }
137
- else if (items.length > 20 && displayLabel.length > 12) {
138
- displayLabel = displayLabel.substring(0, 11) + "...";
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 if (items.length > 12 && displayLabel.length > 15) {
141
- displayLabel = displayLabel.substring(0, 14) + "...";
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 }), _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 })] }, item.id));
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 * 1.2,
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
@@ -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;CACpD"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-spin-prize",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Modern, customizable prize wheel spinner component for React with TypeScript support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",