Live Presence Stack
motiq/live-presence-stackFreeComponent
A live avatar stack for collaborative workspaces: animated join/leave without row reflow, per-user active/idle/editing/viewing status (dot + label, never color alone), an overflow +N count, and a keyboard-accessible participant detail popover. Controlled presence data.
Live preview
live · rendered by the registry
Rendered by motiq on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://www.motiq.dev/r/live-presence-stack.jsonSource
1"use client";23import * as React from "react";4import { motion, AnimatePresence, type Transition } from "motion/react";56import { cn } from "@/lib/utils";7import { useReducedMotion, useVisibilityPause } from "@/lib/motiq";89/* --------------------------------------------------------------------------10 * LivePresenceStack — a live avatar stack for collaborative workspaces.11 *12 * Shows who is present, animates joins/leaves without reflowing the row13 * (layout animation + a reserved slot for the "+N" overflow), reflects a14 * per-user status (active / idle / editing / viewing) with a shape + label15 * (never color alone), and opens a keyboard-accessible detail popover that16 * lists every participant with their status in words.17 *18 * Presence is fully controlled: pass `users` and the stack renders it. It19 * holds no presence state of its own, so it composes with any realtime source.20 * Clean-room original.21 * ----------------------------------------------------------------------- */2223export type PresenceStatus = "active" | "idle" | "editing" | "viewing";2425export interface PresenceUser {26 /** Stable identity — drives join/leave animation and React keys. */27 id: string;28 /** Human name; also the accessible name of the avatar. */29 name: string;30 status: PresenceStatus;31 /** Optional explicit avatar color (any CSS color). Defaults to a hue derived from the name. */32 color?: string;33}3435export interface LivePresenceStackProps {36 /** Controlled presence data (source of truth). */37 users: PresenceUser[];38 /** Maximum avatars shown before collapsing into a "+N" chip. */39 max?: number;40 /** Called when a participant row is chosen in the detail popover. */41 onSelect?: (userId: string) => void;42 /** Accessible group label. Defaults to "N people here". */43 label?: string;44 className?: string;45}4647const STATUS_META: Record<PresenceStatus, { label: string; dot: string; live: boolean }> = {48 active: { label: "Active", dot: "var(--color-success)", live: true },49 editing: { label: "Editing", dot: "var(--color-accent)", live: true },50 viewing: { label: "Viewing", dot: "var(--color-fg)", live: false },51 idle: { label: "Idle", dot: "var(--color-muted)", live: false },52};5354const EASE: Transition["ease"] = [0.2, 0, 0, 1];5556/** Deterministic hue from a name so colors are stable across renders/sessions. */57function hueFromName(name: string): number {58 let h = 0;59 for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;60 return h % 360;61}6263// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from motiq
All 100 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Activity Streamactivity-stream | motiq | Components | Free | 1 dep | |
| Adaptive Safe-Zone Gridadaptive-safe-zone-grid | motiq | Components | Free | no deps | |
| Agent Run Timelineagent-run-timeline | motiq | Components | Free | 1 dep | |
| AI Response Streamai-response-stream | motiq | Components | Free | 1 dep | |
| Animated Accordionanimated-accordion | motiq | Components | Free | 2 deps | |
| Animated Buttonanimated-button | motiq | Components | Free | 1 dep | |
| Animated Dialoganimated-dialog | motiq | Components | Free | 2 deps | |
| Animated Gridanimated-grid | motiq | Components | Free | 2 deps |
