# react-slotmachine 🎰

> A fully customizable React + TypeScript slot machine component — a 1:1 port of [jQuery-SlotMachine](https://github.com/josex2r/jQuery-SlotMachine) by josex2r.

[![npm version](https://img.shields.io/npm/v/react-slotmachine)](https://www.npmjs.com/package/react-slotmachine)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Built for raffles, giveaways, casino games, prize draws, or any "spin to win" UX.  
No jQuery dependency. Fully tree-shakeable. Ships with CSS, TypeScript types, and an imperative API.

---

## ✨ Features

- 🎯 **1:1 port** of jQuery-SlotMachine v6 behavior (shuffle, stop, next, prev, speed tiers, blur effects, gradient mask).
- ⚛️ **React-first**: Component + hook (`useSlotMachineEngine`) for headless usage.
- 🎨 **Fully customizable**: Slot size, border radius, background, box shadow, buttons, icons, labels, gradient mask, blur effects — all tweakable via props.
- 🧩 **Imperative handle** via `ref` for programmatic control (`shuffle()`, `stop()`, `next()`, `prev()`).
- 🌀 **Seamless wraparound** animation — items appear to spin infinitely.
- 🏷️ **TypeScript** — comprehensive type exports.
- 📦 **Tree-shakeable** ESM + CJS builds via `tsup`.
- 🎬 **Speed tiers** matching the original: fast → normal → slow → turtle → stop.
- 🎢 **Smooth easing**: ease-in acceleration at spin start + ease-out deceleration before landing for a natural slot-machine feel.

---

## 📦 Installation

```bash
npm install @firdaus.lubis/react-slotmachine
# or
yarn add @firdaus.lubis/react-slotmachine
# or
pnpm add @firdaus.lubis/react-slotmachine
```

**Peer dependencies**: `react` >= 16.8.0, `react-dom` >= 16.8.0.

---

## 🚀 Quick start

```tsx
import { SlotMachine } from '@firdaus.lubis/react-slotmachine';
import '@firdaus.lubis/react-slotmachine/styles.css';

function App() {
  return (
    <SlotMachine
      items={['🍎 Apple', '🍌 Banana', '🍒 Cherry', '🍇 Grape', '🍊 Orange']}
      options={{ active: 0, delay: 300, direction: 'up' }}
      onStop={({ item, index }) => {
        console.log(`🏆 Winner: ${item} (index ${index})`);
      }}
    />
  );
}
```

---

## 🧩 Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `items` | `ReactNode[]` | **required** | Array of slot items (strings, elements, or custom nodes). |
| `itemKeys` | `(string \| number)[]` | — | Optional keys/data associated 1:1 with `items`. Passed through in `onStop` as `key` so you can identify the winner without relying on array indices. |
| `initialItem` | `ReactNode` | — | Special item shown at starting position (index 0). Never chosen as winner — only `items` are eligible. Also prevents the slot from appearing empty before the first shuffle. |
| `options` | `SlotMachineOptions` | `{}` | Engine options (see below). |
| `onStop` | `(winner: { item, index, key? }) => void` | — | Called when the machine stops on a winner. `key` is the corresponding `itemKeys` value (if provided). |
| `onShuffle` | `(nextIndex: number) => void` | — | Called on every shuffle tick (useful for reactive UIs). |
| `className` | `string` | — | Additional CSS class for the outer wrapper. |
| `style` | `CSSProperties` | — | Inline styles for the outer wrapper. |
| `slotHeight` | `string` | `'100px'` | Height of the visible slot window. |
| `slotWidth` | `string` | `'100%'` | Width of the slot window. |
| `borderRadius` | `string` | `'12px'` | Border radius of the slot window. |
| `slotBackground` | `string` | `'#ffffff'` | Background color of the slot area. |
| `slotBoxShadow` | `string` | `'0 4px 24px rgba(0,0,0,0.12)'` | Box shadow of the slot window. |
| `enableGradientMask` | `boolean` | `true` | Fade gradient on top/bottom edges. |
| `enableBlur` | `boolean` | `true` | Blur effects on tiles while spinning. |
| `shuffleLabel` | `string` | `'Shuffle!'` | Text for the shuffle button. |
| `stopLabel` | `string` | `'Stop!'` | Text for the stop button. |
| `hideButtons` | `boolean` | `false` | Hide built-in buttons (use imperative handle instead). |
| `shuffleButtonClassName` | `string` | — | Custom class for the shuffle button. |
| `stopButtonClassName` | `string` | — | Custom class for the stop button. |
| `buttonsClassName` | `string` | — | Custom class for the buttons container. |
| `shuffleIcon` | `ReactNode` | 🔀 SVG | Custom icon for shuffle button. |
| `stopIcon` | `ReactNode` | ⏹ SVG | Custom icon for stop button. |
| `tileClassName` | `string` | — | Custom class applied to each tile wrapper. |

---

## ⚙️ Engine Options (`SlotMachineOptions`)

| Option | Type | Default | Description |
|---|---|---|---|
| `active` | `number` | `0` | Initial visible element index (0-based). |
| `delay` | `number` | `200` | Duration (ms) of each spin tick. |
| `direction` | `'up' \| 'down'` | `'up'` | Animation scroll direction. |
| `randomize` | `(active, total) => number` | uniform random | Custom function to determine the next active index. |
| `easing` | `boolean` | `true` | Enable ease-in (acceleration) and ease-out (deceleration) phases. Set `false` for constant-speed spins. |
| `easeDuration` | `number` | `600` | Duration in ms of each easing phase. Higher = longer acceleration/deceleration. |

---

## 🎮 Imperative API (via `ref`)

```tsx
import { useRef } from 'react';
import { SlotMachine, type SlotMachineHandle } from '@firdaus.lubis/react-slotmachine';

function App() {
  const machineRef = useRef<SlotMachineHandle>(null);

  return (
    <>
      <SlotMachine ref={machineRef} items={['A', 'B', 'C']} hideButtons />
      <button onClick={() => machineRef.current?.shuffle(999999)}>Spin!</button>
      <button onClick={() => machineRef.current?.stop(0)}>Stop!</button>
      <button onClick={() => machineRef.current?.next()}>Next</button>
      <button onClick={() => machineRef.current?.prev()}>Prev</button>
    </>
  );
}
```

### `SlotMachineHandle`

| Method / Property | Type | Description |
|---|---|---|
| `shuffle(spins?)` | `(spins?: number) => void` | Start spinning. Default 5 spins. Use `Infinity` for endless. |
| `stop(spins?)` | `(spins?: number) => void` | Schedule stop after N more spins (0 = immediate). |
| `next()` | `() => void` | Spin to the next element. |
| `prev()` | `() => void` | Spin to the previous element. |
| `active` | `number` (readonly) | Current active index. |
| `running` | `boolean` (readonly) | Whether the machine is spinning. |
| `stopping` | `boolean` (readonly) | Whether the machine is decelerating. |

---

## 🪝 Headless usage (`useSlotMachineEngine`)

For full control over rendering, use the engine hook directly:

```tsx
import { useSlotMachineEngine } from '@firdaus.lubis/react-slotmachine';

function CustomSlotMachine() {
  const { active, running, shuffle, stop } = useSlotMachineEngine(5, {
    active: 0,
    delay: 300,
  });

  return (
    <div>
      <div style={{ /* your custom rendering */ }}>
        Current: {active}
      </div>
      <button onClick={() => shuffle(10)} disabled={running}>
        {running ? 'Spinning...' : 'Shuffle'}
      </button>
      <button onClick={() => stop(0)}>Stop</button>
    </div>
  );
}
```

---

## 🎨 Customization examples

### Dark themed raffle

```tsx
<SlotMachine
  items={['🏆 Prize 1', '💰 Prize 2', '🎁 Prize 3']}
  options={{ active: 0, delay: 250 }}
  slotHeight="120px"
  slotWidth="400px"
  borderRadius="20px"
  slotBackground="#1a1a2e"
  slotBoxShadow="0 0 40px rgba(255,215,0,0.3)"
  shuffleLabel="Draw!"
  stopLabel="Pick Winner!"
  enableGradientMask={false}
/>
```

### Controlled via custom buttons

```tsx
<SlotMachine
  ref={machineRef}
  items={names}
  hideButtons
  onStop={({ item }) => alert(`Winner: ${item}`)}
/>
<button onClick={() => machineRef.current?.shuffle(Infinity)}>Start</button>
<button onClick={() => machineRef.current?.stop(3)}>Stop in 3</button>
```

### Pre-determined winner (`randomize`)

```tsx
<SlotMachine
  items={users}
  options={{
    randomize: () => 2, // always land on index 2
  }}
/>
```

### With a starting placeholder (`initialItem`)

```tsx
<SlotMachine
  initialItem={<em>🎰 Spin to win!</em>}
  items={['🏆 Gold', '🥈 Silver', '🥉 Bronze']}
  options={{ active: 0, delay: 300 }}
  onStop={({ item, index }) => alert(`Winner: ${item}`)}
/>
```

---

## 🧪 Development

```bash
git clone https://github.com/@firdaus.lubis/react-slotmachine.git
cd react-slotmachine
pnpm install
pnpm dev        # watch mode
pnpm build      # production build
```

---

## 📄 License

MIT © [ifexindonesia](https://github.com/@firdaus.lubis/react-slotmachine)

---

## 🙏 Acknowledgements

This package is a React port of the excellent [jQuery-SlotMachine](https://github.com/josex2r/jQuery-SlotMachine) by [Jose Luis Represa](https://github.com/josex2r). The original library is also MIT licensed.
