# @hiyve/react-reactions

Visual reactions and effects overlay for Hiyve video conferencing rooms — floating emojis, confetti, fireworks, and custom animations synced across all participants via WebRTC data messages.

## Installation

```bash
npm install @hiyve/react-reactions
```

**Peer dependencies:**

```bash
npm install @hiyve/react @hiyve/utilities @mui/material @mui/icons-material @emotion/react @emotion/styled react
```

## Quick Start

```tsx
import { HiyveProvider } from '@hiyve/react';
import { useReactionSync, ReactionsOverlay, ReactionPicker, DEFAULT_EFFECTS } from '@hiyve/react-reactions';

function Room() {
  const { triggerReaction, canTrigger, isCoolingDown, cooldownRemaining, activeEffect, clearEffect } =
    useReactionSync({
      localUserId: 'user-123',
      isOwner: true,
      permission: 'everyone',
    });

  return (
    <>
      <ReactionsOverlay activeEffect={activeEffect} onEffectComplete={clearEffect} />
      {canTrigger && (
        <ReactionPicker
          effects={DEFAULT_EFFECTS}
          onSelect={triggerReaction}
          disabled={isCoolingDown}
        />
      )}
    </>
  );
}

function App() {
  return (
    <HiyveProvider apiKey="your-api-key" roomName="my-room">
      <Room />
    </HiyveProvider>
  );
}
```

## Components

| Component | Description |
|-----------|-------------|
| `ReactionsOverlay` | Full-screen overlay that renders the currently active effect (floating emojis, confetti, fireworks, or custom) |
| `ReactionPicker` | Popover menu for selecting a reaction effect, with cooldown-aware disabled state |
| `FloatingEmoji` | CSS-animated floating emoji/image effect renderer |
| `ConfettiEffect` | Canvas-based confetti burst effect renderer |
| `FireworksEffect` | Canvas-based fireworks with rockets and exploding sparks |
| `SnowballEffect` | Projectile animation with grow and splat phases |

## Hooks

| Hook | Description |
|------|-------------|
| `useReactionSync` | Core hook — sends and receives reactions via data messages, manages permissions, cooldown, deduplication, and active effect state |
| `useReactionThrottle` | Manages cooldown state and countdown display (used internally by `useReactionSync`) |

## Built-in Effects

9 effects included out of the box via `DEFAULT_EFFECTS`:

| ID | Label | Type | Preview |
|----|-------|------|---------|
| `heart` | Hearts | floating | ❤️ |
| `star` | Stars | floating | ⭐ |
| `thumbsup` | Thumbs Up | floating | 👍 |
| `party` | Party | floating | 🎉 |
| `fire` | Fire | floating | 🔥 |
| `clap` | Applause | floating | 👏 |
| `music` | Music | floating | 🎵 |
| `confetti` | Confetti | fullscreen | 🎊 |
| `fireworks` | Fireworks | fireworks | 🎆 |

## Permissions

Control who can trigger reactions via the `permission` option on `useReactionSync`:

```tsx
// Only room owner (default)
useReactionSync({ localUserId, isOwner, permission: 'owner-only' });

// Any participant
useReactionSync({ localUserId, isOwner, permission: 'everyone' });

// Custom logic
useReactionSync({
  localUserId,
  isOwner,
  permission: (userId, isOwner) => isOwner || userId.endsWith('@admin.com'),
});
```

## Custom Effects

Register custom effects by providing a `renderer` component:

```tsx
import type { CustomEffectProps, ReactionEffect } from '@hiyve/react-reactions';

function RainEffect({ onComplete, width, height }: CustomEffectProps) {
  // Your custom animation logic
  useEffect(() => {
    const timer = setTimeout(onComplete, 4000);
    return () => clearTimeout(timer);
  }, [onComplete]);

  return <canvas width={width} height={height} />;
}

const customEffects: ReactionEffect[] = [
  ...DEFAULT_EFFECTS,
  {
    id: 'rain',
    label: 'Rain',
    type: 'custom',
    renderer: RainEffect,
    thumbnail: '🌧️',
    duration: 4000,
  },
];

// Pass to useReactionSync
useReactionSync({ localUserId, isOwner, effects: customEffects });
```

## Customization

### ReactionPicker Labels, Colors, and Styles

```tsx
<ReactionPicker
  effects={DEFAULT_EFFECTS}
  onSelect={triggerReaction}
  labels={{ tooltip: 'Send a reaction' }}
  colors={{ background: '#1a1a2e', hoverBackground: '#16213e', triggerColor: '#fff' }}
  styles={{ columns: 3, itemSize: 56 }}
  triggerIcon={<span>😀</span>}
/>
```

### ReactionsOverlay

```tsx
<ReactionsOverlay
  activeEffect={activeEffect}
  onEffectComplete={clearEffect}
  zIndex={3000}
/>
```

## Props

### `useReactionSync` Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `localUserId` | `string` | **required** | Local user identifier |
| `isOwner` | `boolean` | **required** | Whether the local user is the room owner |
| `permission` | `ReactionPermission` | `'owner-only'` | Who can trigger reactions |
| `effects` | `ReactionEffect[]` | `DEFAULT_EFFECTS` | Available effects catalog |
| `cooldownMs` | `number` | `4000` | Cooldown between triggers in ms |
| `onReactionTriggered` | `(effectId, triggeredBy) => void` | — | Called when any participant triggers a reaction |
| `onError` | `(error: Error) => void` | — | Called when an error occurs during sending or processing |

### `useReactionSync` Return

| Property | Type | Description |
|----------|------|-------------|
| `triggerReaction` | `(effectId: string) => void` | Broadcast an effect to all participants |
| `canTrigger` | `boolean` | Whether the local user has permission |
| `isCoolingDown` | `boolean` | Whether the cooldown is active |
| `cooldownRemaining` | `number` | Seconds remaining in cooldown |
| `activeEffect` | `ReactionEffect \| null` | Currently playing effect |
| `clearEffect` | `() => void` | Dismiss the active effect early |

### ReactionsOverlayProps

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `activeEffect` | `ReactionEffect \| null` | **required** | Effect to render |
| `onEffectComplete` | `() => void` | **required** | Called when animation finishes |
| `zIndex` | `number` | `2000` | Overlay z-index |

### ReactionPickerProps

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `effects` | `ReactionEffect[]` | **required** | Available effects |
| `onSelect` | `(effectId: string) => void` | **required** | Called on effect selection |
| `disabled` | `boolean` | `false` | Disable during cooldown |
| `labels` | `Partial<ReactionPickerLabels>` | — | Label overrides |
| `colors` | `Partial<ReactionPickerColors>` | — | Color overrides |
| `styles` | `Partial<ReactionPickerStyles>` | — | Style overrides |
| `triggerIcon` | `ReactNode` | — | Custom trigger button icon |
| `tooltip` | `string` | `'Reactions'` | Trigger button tooltip |

## Requirements

- React 18+
- `@hiyve/react` — must be inside `<HiyveProvider>`
- `@mui/material` v5 or v6 + Emotion
