react-raffle-picker 0.3.0 → 0.3.2
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 +29 -18
- package/dist/index.cjs +6 -2
- package/dist/index.mjs +6 -2
- package/dist/styles.css +43 -13
- package/package.json +19 -9
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
<h1>react-raffle-picker</h1>
|
|
5
5
|
|
|
6
|
-
<p><strong>React
|
|
6
|
+
<p><strong>Random winner picker for React.</strong><br/>Giveaways, raffles, prize draws and slot-machine UIs — headless, typed, zero dependencies.</p>
|
|
7
7
|
|
|
8
8
|
<p>
|
|
9
9
|
<a href="https://react-raffle-one.vercel.app/">Live demo</a>
|
|
@@ -15,16 +15,28 @@
|
|
|
15
15
|
<a href="https://kirilinsky.github.io/react-raffle-picker/">
|
|
16
16
|
<img src="https://img.shields.io/badge/storybook-live-ff4785?logo=storybook&logoColor=white" alt="Storybook" />
|
|
17
17
|
</a>
|
|
18
|
-
<a href="https://codecov.io/gh/kirilinsky/react-raffle-picker">
|
|
19
|
-
<img src="https://
|
|
18
|
+
<a href="https://app.codecov.io/gh/kirilinsky/react-raffle-picker">
|
|
19
|
+
<img src="https://img.shields.io/codecov/c/github/kirilinsky/react-raffle-picker?label=coverage" alt="Codecov coverage" />
|
|
20
20
|
</a>
|
|
21
|
-
<a href="https://
|
|
22
|
-
<img src="https://img.shields.io/
|
|
21
|
+
<a href="https://www.npmjs.com/package/react-raffle-picker">
|
|
22
|
+
<img src="https://img.shields.io/npm/v/react-raffle-picker" alt="npm version" />
|
|
23
|
+
</a>
|
|
24
|
+
<a href="https://www.npmjs.com/package/react-raffle-picker">
|
|
25
|
+
<img src="https://img.shields.io/npm/dm/react-raffle-picker" alt="npm downloads" />
|
|
23
26
|
</a>
|
|
24
27
|
</p>
|
|
25
28
|
</div>
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
Pick a random winner in React. `react-raffle-picker` cycles a list of names or a number range,
|
|
31
|
+
freezes on a winner, and never draws the same one twice — with slot-machine reels, four CSS
|
|
32
|
+
animations and countdown auto-freeze.
|
|
33
|
+
|
|
34
|
+
It ships as a **headless compound component**: no layout opinions, no style props soup, you
|
|
35
|
+
bring your own CSS. Performant on slow devices — high-frequency tick updates bypass React and
|
|
36
|
+
write to the DOM via refs.
|
|
37
|
+
|
|
38
|
+
**Use it for:** giveaway winners on stream · prize draws · random name picker · standup speaking
|
|
39
|
+
order · random number roller · slot-machine and lottery UIs.
|
|
28
40
|
|
|
29
41
|
```bash
|
|
30
42
|
npm install react-raffle-picker
|
|
@@ -68,7 +80,6 @@ This means:
|
|
|
68
80
|
|
|
69
81
|
```tsx
|
|
70
82
|
import { RafflePick } from 'react-raffle-picker'
|
|
71
|
-
|
|
72
83
|
;<RafflePick min={1} max={100} interval={100} inertia onSelect={(v) => console.log(v)}>
|
|
73
84
|
<RafflePick.Value animation="roll" className="my-value" />
|
|
74
85
|
<RafflePick.Button startLabel="Pick" stopLabel="Stop" />
|
|
@@ -104,11 +115,11 @@ Provides context. Renders an optional wrapper element (`as` prop, default `'div'
|
|
|
104
115
|
| `random` | `boolean` | `true` | Random pick vs sequential. |
|
|
105
116
|
| `inertia` | `boolean` | `false` | Soft start / soft stop ramp. |
|
|
106
117
|
| `autoStart` | `boolean` | `true` | Begin cycling on mount. |
|
|
107
|
-
| `noRepeat` | `boolean` | `true` | Exclude previously frozen values from later rounds — no duplicate winners across sequential draws in the same mounted instance. Set `false` to allow repeats.
|
|
118
|
+
| `noRepeat` | `boolean` | `true` | Exclude previously frozen values from later rounds — no duplicate winners across sequential draws in the same mounted instance. Set `false` to allow repeats. |
|
|
108
119
|
| `initialValue` | `number \| string` | — | Starting display before first run. Number for `min`/`max` mode, string for `items` mode. For `<Slots>`, each character seeds the corresponding reel. |
|
|
109
120
|
| `finalValue` | `number \| string` | — | Forces settle to land on this value. Cycle still appears random; only final freeze is rigged. For `<Slots>`, each character is the final char of the corresponding reel. |
|
|
110
121
|
| `onSelect` | `(value) => void` | — | Fires once per round on freeze. |
|
|
111
|
-
| `onExhausted` | `() => void` | — | Fires when `start()` is called but `noRepeat` has already drawn every candidate.
|
|
122
|
+
| `onExhausted` | `() => void` | — | Fires when `start()` is called but `noRepeat` has already drawn every candidate. |
|
|
112
123
|
| `as` | `ElementType` | `'div'` | Wrapper tag. |
|
|
113
124
|
| `className` | `string` | — | Wrapper class. |
|
|
114
125
|
| `style` | `CSSProperties` | — | Wrapper style. |
|
|
@@ -133,15 +144,15 @@ Multiple `Value` instances inside one root are supported — all subscribe to th
|
|
|
133
144
|
|
|
134
145
|
Toggles start / freeze based on phase. Disabled during settling.
|
|
135
146
|
|
|
136
|
-
| Prop | Type | Notes
|
|
137
|
-
| ------------ | --------------- |
|
|
138
|
-
| `startLabel` | `ReactNode` | Shown in `idle` / `frozen` (click starts).
|
|
139
|
-
| `stopLabel` | `ReactNode` | Shown in `running` / `starting` (click stops).
|
|
140
|
-
| `waitLabel` | `ReactNode` | Shown in `settling` (button disabled).
|
|
141
|
-
| `children` | `ReactNode` | Fallback label when state-specific label absent.
|
|
147
|
+
| Prop | Type | Notes |
|
|
148
|
+
| ------------ | --------------- | --------------------------------------------------------------- |
|
|
149
|
+
| `startLabel` | `ReactNode` | Shown in `idle` / `frozen` (click starts). |
|
|
150
|
+
| `stopLabel` | `ReactNode` | Shown in `running` / `starting` (click stops). |
|
|
151
|
+
| `waitLabel` | `ReactNode` | Shown in `settling` (button disabled). |
|
|
152
|
+
| `children` | `ReactNode` | Fallback label when state-specific label absent. |
|
|
142
153
|
| `disabled` | `boolean` | External disable, on top of the auto-disable during `settling`. |
|
|
143
|
-
| `className` | `string` | —
|
|
144
|
-
| `style` | `CSSProperties` | —
|
|
154
|
+
| `className` | `string` | — |
|
|
155
|
+
| `style` | `CSSProperties` | — |
|
|
145
156
|
|
|
146
157
|
### `<RafflePick.Countdown>`
|
|
147
158
|
|
|
@@ -257,7 +268,7 @@ function Giveaway() {
|
|
|
257
268
|
## Accessibility
|
|
258
269
|
|
|
259
270
|
- `<RafflePick.Value>` cycles are visual-only (`aria-hidden`) — high-frequency tick
|
|
260
|
-
updates are not announced. The frozen result
|
|
271
|
+
updates are not announced. The frozen result _is_ announced once per round via a
|
|
261
272
|
hidden `aria-live="polite"` region.
|
|
262
273
|
- `<RafflePick.Countdown>`'s ring/label are `aria-hidden`; a one-time sr-only
|
|
263
274
|
announcement fires when the countdown starts. The result itself is still
|
package/dist/index.cjs
CHANGED
|
@@ -534,10 +534,14 @@ const SLOT_BASE_CSS = `
|
|
|
534
534
|
.rrp-slot{display:inline-block;position:relative;overflow:hidden;vertical-align:baseline}
|
|
535
535
|
.rrp-slot__col{position:absolute;top:0;left:0;right:0;height:300%;transform:translate3d(0,-33.3333%,0);animation:rrp-slot-reel var(--rrp-tick,80ms) linear infinite;will-change:transform}
|
|
536
536
|
.rrp-slot__cell{height:33.3333%;display:grid;place-items:center;padding:0;margin:0;box-sizing:border-box;text-align:center}
|
|
537
|
-
.rrp-slot[data-stopped] .rrp-slot__col{animation:
|
|
537
|
+
.rrp-slot[data-stopped] .rrp-slot__col{animation:rrp-slot-settle 320ms cubic-bezier(0.2,1.4,0.4,1) forwards}
|
|
538
538
|
@keyframes rrp-slot-reel{from{transform:translate3d(0,-33.3333%,0)}to{transform:translate3d(0,-66.6666%,0)}}
|
|
539
|
+
@keyframes rrp-slot-settle{0%{transform:translate3d(0,-38%,0)}60%{transform:translate3d(0,-31%,0)}100%{transform:translate3d(0,-33.3333%,0)}}
|
|
540
|
+
@media (prefers-reduced-motion: reduce) {
|
|
541
|
+
.rrp-slot__col { animation: none !important; }
|
|
542
|
+
}
|
|
539
543
|
`;
|
|
540
|
-
const SLOT_STYLES_VERSION = "
|
|
544
|
+
const SLOT_STYLES_VERSION = "5";
|
|
541
545
|
const injectSlotStyles = () => {
|
|
542
546
|
if (typeof document === "undefined") return;
|
|
543
547
|
const existing = document.querySelector("style[data-rrp-slot-base]");
|
package/dist/index.mjs
CHANGED
|
@@ -533,10 +533,14 @@ const SLOT_BASE_CSS = `
|
|
|
533
533
|
.rrp-slot{display:inline-block;position:relative;overflow:hidden;vertical-align:baseline}
|
|
534
534
|
.rrp-slot__col{position:absolute;top:0;left:0;right:0;height:300%;transform:translate3d(0,-33.3333%,0);animation:rrp-slot-reel var(--rrp-tick,80ms) linear infinite;will-change:transform}
|
|
535
535
|
.rrp-slot__cell{height:33.3333%;display:grid;place-items:center;padding:0;margin:0;box-sizing:border-box;text-align:center}
|
|
536
|
-
.rrp-slot[data-stopped] .rrp-slot__col{animation:
|
|
536
|
+
.rrp-slot[data-stopped] .rrp-slot__col{animation:rrp-slot-settle 320ms cubic-bezier(0.2,1.4,0.4,1) forwards}
|
|
537
537
|
@keyframes rrp-slot-reel{from{transform:translate3d(0,-33.3333%,0)}to{transform:translate3d(0,-66.6666%,0)}}
|
|
538
|
+
@keyframes rrp-slot-settle{0%{transform:translate3d(0,-38%,0)}60%{transform:translate3d(0,-31%,0)}100%{transform:translate3d(0,-33.3333%,0)}}
|
|
539
|
+
@media (prefers-reduced-motion: reduce) {
|
|
540
|
+
.rrp-slot__col { animation: none !important; }
|
|
541
|
+
}
|
|
538
542
|
`;
|
|
539
|
-
const SLOT_STYLES_VERSION = "
|
|
543
|
+
const SLOT_STYLES_VERSION = "5";
|
|
540
544
|
const injectSlotStyles = () => {
|
|
541
545
|
if (typeof document === "undefined") return;
|
|
542
546
|
const existing = document.querySelector("style[data-rrp-slot-base]");
|
package/dist/styles.css
CHANGED
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
box-sizing: border-box;
|
|
28
28
|
text-align: center;
|
|
29
29
|
}
|
|
30
|
+
/* Stop = quick decelerate-and-settle bounce, not an instant snap. */
|
|
30
31
|
.rrp-slot[data-stopped] .rrp-slot__col {
|
|
31
|
-
animation:
|
|
32
|
-
transform: translate3d(0, -33.3333%, 0);
|
|
32
|
+
animation: rrp-slot-settle 320ms cubic-bezier(0.2, 1.4, 0.4, 1) forwards;
|
|
33
33
|
}
|
|
34
34
|
@keyframes rrp-slot-reel {
|
|
35
35
|
from {
|
|
@@ -39,16 +39,27 @@
|
|
|
39
39
|
transform: translate3d(0, -66.6666%, 0);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
@keyframes rrp-slot-settle {
|
|
43
|
+
0% {
|
|
44
|
+
transform: translate3d(0, -38%, 0);
|
|
45
|
+
}
|
|
46
|
+
60% {
|
|
47
|
+
transform: translate3d(0, -31%, 0);
|
|
48
|
+
}
|
|
49
|
+
100% {
|
|
50
|
+
transform: translate3d(0, -33.3333%, 0);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
42
53
|
|
|
43
54
|
/* Value animations: opt-in via <RafflePick.Value animation="..." /> */
|
|
44
55
|
.rrp-value {
|
|
45
56
|
display: inline-block;
|
|
46
57
|
}
|
|
47
58
|
.rrp-value[data-animation='roll'][data-phase='running'] {
|
|
48
|
-
animation: rrp-value-roll var(--rrp-tick, 100ms)
|
|
59
|
+
animation: rrp-value-roll var(--rrp-tick, 100ms) cubic-bezier(0.22, 1, 0.36, 1);
|
|
49
60
|
}
|
|
50
61
|
.rrp-value[data-animation='fade'][data-phase='running'] {
|
|
51
|
-
animation: rrp-value-fade var(--rrp-tick, 100ms)
|
|
62
|
+
animation: rrp-value-fade var(--rrp-tick, 100ms) cubic-bezier(0.16, 1, 0.3, 1);
|
|
52
63
|
}
|
|
53
64
|
.rrp-value[data-animation='blur'][data-phase='running'] {
|
|
54
65
|
animation: rrp-value-blur var(--rrp-tick, 100ms) linear;
|
|
@@ -56,21 +67,25 @@
|
|
|
56
67
|
.rrp-value[data-animation='reel'][data-phase='running'] {
|
|
57
68
|
animation: rrp-value-reel var(--rrp-tick, 100ms) linear;
|
|
58
69
|
}
|
|
70
|
+
/* Freeze = one settle pop shared by every animation type — the "winner" beat. */
|
|
71
|
+
.rrp-value[data-animation][data-phase='frozen'] {
|
|
72
|
+
animation: rrp-value-settle 420ms cubic-bezier(0.2, 1.4, 0.4, 1);
|
|
73
|
+
}
|
|
59
74
|
@keyframes rrp-value-roll {
|
|
60
|
-
|
|
75
|
+
0% {
|
|
61
76
|
transform: translateY(-0.4em);
|
|
62
|
-
opacity: 0.
|
|
77
|
+
opacity: 0.45;
|
|
63
78
|
}
|
|
64
|
-
|
|
79
|
+
100% {
|
|
65
80
|
transform: translateY(0);
|
|
66
81
|
opacity: 1;
|
|
67
82
|
}
|
|
68
83
|
}
|
|
69
84
|
@keyframes rrp-value-fade {
|
|
70
|
-
|
|
71
|
-
opacity: 0.
|
|
85
|
+
0% {
|
|
86
|
+
opacity: 0.25;
|
|
72
87
|
}
|
|
73
|
-
|
|
88
|
+
100% {
|
|
74
89
|
opacity: 1;
|
|
75
90
|
}
|
|
76
91
|
}
|
|
@@ -92,13 +107,28 @@
|
|
|
92
107
|
opacity: 1;
|
|
93
108
|
}
|
|
94
109
|
}
|
|
110
|
+
@keyframes rrp-value-settle {
|
|
111
|
+
0% {
|
|
112
|
+
transform: scale(0.84);
|
|
113
|
+
opacity: 0.7;
|
|
114
|
+
}
|
|
115
|
+
55% {
|
|
116
|
+
transform: scale(1.07);
|
|
117
|
+
opacity: 1;
|
|
118
|
+
}
|
|
119
|
+
100% {
|
|
120
|
+
transform: scale(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
95
123
|
|
|
96
|
-
/* Respect vestibular disorders — kill all motion, keep instant value swaps.
|
|
124
|
+
/* Respect vestibular disorders — kill all motion, keep instant value swaps.
|
|
125
|
+
!important: these must always win, regardless of the specificity of
|
|
126
|
+
whatever running/settle rule above (or added later) also matches. */
|
|
97
127
|
@media (prefers-reduced-motion: reduce) {
|
|
98
128
|
.rrp-slot__col {
|
|
99
|
-
animation: none;
|
|
129
|
+
animation: none !important;
|
|
100
130
|
}
|
|
101
131
|
.rrp-value[data-animation] {
|
|
102
|
-
animation: none;
|
|
132
|
+
animation: none !important;
|
|
103
133
|
}
|
|
104
134
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-raffle-picker",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "React random picker: draw a random winner or number with slot-machine, reel and countdown animations. Headless, typed, zero-dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -61,18 +61,28 @@
|
|
|
61
61
|
},
|
|
62
62
|
"keywords": [
|
|
63
63
|
"react",
|
|
64
|
-
"raffle",
|
|
65
|
-
"giveaway",
|
|
66
|
-
"winner",
|
|
67
|
-
"picker",
|
|
68
64
|
"random-picker",
|
|
69
|
-
"
|
|
65
|
+
"random-name-picker",
|
|
66
|
+
"name-picker",
|
|
67
|
+
"winner-picker",
|
|
68
|
+
"random-winner",
|
|
69
|
+
"giveaway",
|
|
70
|
+
"giveaway-picker",
|
|
71
|
+
"prize-draw",
|
|
72
|
+
"raffle",
|
|
73
|
+
"raffle-picker",
|
|
70
74
|
"lottery",
|
|
71
75
|
"slot-machine",
|
|
76
|
+
"slot-machine-react",
|
|
77
|
+
"reel",
|
|
78
|
+
"number-picker",
|
|
79
|
+
"random-number",
|
|
72
80
|
"countdown",
|
|
73
81
|
"headless",
|
|
82
|
+
"headless-ui",
|
|
74
83
|
"compound-components",
|
|
75
|
-
"react-component"
|
|
84
|
+
"react-component",
|
|
85
|
+
"typescript"
|
|
76
86
|
],
|
|
77
87
|
"author": "Kirilinsky (https://github.com/kirilinsky)",
|
|
78
88
|
"license": "MIT",
|
|
@@ -115,4 +125,4 @@
|
|
|
115
125
|
"volta": {
|
|
116
126
|
"node": "22.12.0"
|
|
117
127
|
}
|
|
118
|
-
}
|
|
128
|
+
}
|