# @cloudsignal/collaborate

> React collaboration primitives powered by CloudSignal MQTT

## Install
npm install @cloudsignal/collaborate @cloudsignal/mqtt-client

## Usage
Wrap with <Space>, use hooks/components inside.

## Provider
Space: id, connection, userName, userColor?, userAvatar?, userData?, debug?
  connection: { host, username?, password?, organizationId?, secretKey?, externalToken?, tokenServiceUrl? }

## Hooks
useSpace() → { spaceId, self, isConnected, isConnecting, error, publish }
usePresence() → { members: SpaceUser[], count, self, onJoin, onLeave }
useCursors(opts?) → { cursorsRef, cursors: CursorData[], publishCursor(x,y) }
useLock(componentId) → { isLocked, lockedBy, isLockedByMe, lock(), unlock() }
useTypingIndicator(inputId?) → { typingUsers, startTyping(), stopTyping(), isTyping }
useReactions(opts?) → { reactions: Reaction[], sendReaction(emoji) }
useBroadcast<T>(event?) → { broadcast(data), lastMessage, onMessage(cb) }
useSharedState<T>(key, initial) → [value, setValue]

## Components
AvatarStack: max?, size?, className?
CursorOverlay: throttleMs?, staleMs?, className?, children
TypingIndicator: inputId?, className?
LockIndicator: componentId, className?, children
ReactionBar: emojis?, className?
PresenceBorder: componentId, borderWidth?, className?, children

## Types
SpaceUser: { userId, name, color, avatar?, data?, joinedAt, lastSeen }
CursorData: { userId, name, x, y, color, ts, lastSeen }
Reaction: { id, userId, name, emoji, color, ts }

## MQTT Topics
$spaces/{spaceId}/presence — heartbeats + join/leave
$spaces/{spaceId}/cursors — cursor positions
$spaces/{spaceId}/locks — component lock/unlock
$spaces/{spaceId}/typing — typing indicators
$spaces/{spaceId}/reactions — emoji reactions
$spaces/{spaceId}/broadcast/{event} — custom events
$spaces/{spaceId}/state/{key} — shared state (retained)
