{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "presence-bar",
  "type": "registry:component",
  "title": "Presence Bar",
  "description": "A live facepile for shared docs: status rings, a rolling “viewing” count, faces that spring in and out, and a click to follow someone around the page.",
  "categories": [
    "component",
    "Together"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://coss.com/ui/r/tooltip.json"
  ],
  "files": [
    {
      "path": "registry/devl/presence-bar.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { EyeIcon, XIcon } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { type ReactNode, useEffect, useState } from \"react\";\nimport { Tooltip, TooltipPopup, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\nfunction useSafeReducedMotion() {\n  const prefers = useReducedMotion();\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n  return mounted && !!prefers;\n}\n\nexport type PresenceHue = \"rose\" | \"sky\" | \"amber\" | \"emerald\" | \"violet\" | \"orange\";\n\nexport interface PresencePerson {\n  id: string;\n  name: string;\n  initials: string;\n  hue: PresenceHue;\n  status?: \"active\" | \"idle\";\n  location?: string;\n}\n\nexport interface PresenceBarProps {\n  people: PresencePerson[];\n  title?: string;\n  subtitle?: string;\n  max?: number;\n  following?: string | null;\n  onFollowChange?: (id: string | null) => void;\n  children?: ReactNode | ((following: PresencePerson | null) => ReactNode);\n  className?: string;\n}\n\nexport const presenceHues: Record<PresenceHue, { avatar: string; frame: string; flag: string; tint: string }> = {\n  rose: {\n    avatar: \"bg-rose-100 text-rose-700 dark:bg-rose-950 dark:text-rose-300\",\n    frame: \"border-rose-600\",\n    flag: \"bg-rose-600 text-white\",\n    tint: \"bg-rose-500/8\",\n  },\n  sky: {\n    avatar: \"bg-sky-100 text-sky-700 dark:bg-sky-950 dark:text-sky-300\",\n    frame: \"border-sky-600\",\n    flag: \"bg-sky-600 text-white\",\n    tint: \"bg-sky-500/8\",\n  },\n  amber: {\n    avatar: \"bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-300\",\n    frame: \"border-amber-600\",\n    flag: \"bg-amber-600 text-white\",\n    tint: \"bg-amber-500/10\",\n  },\n  emerald: {\n    avatar: \"bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300\",\n    frame: \"border-emerald-600\",\n    flag: \"bg-emerald-600 text-white\",\n    tint: \"bg-emerald-500/8\",\n  },\n  violet: {\n    avatar: \"bg-violet-100 text-violet-700 dark:bg-violet-950 dark:text-violet-300\",\n    frame: \"border-violet-600\",\n    flag: \"bg-violet-600 text-white\",\n    tint: \"bg-violet-500/8\",\n  },\n  orange: {\n    avatar: \"bg-orange-100 text-orange-700 dark:bg-orange-950 dark:text-orange-300\",\n    frame: \"border-orange-600\",\n    flag: \"bg-orange-600 text-white\",\n    tint: \"bg-orange-500/10\",\n  },\n};\n\nconst spring = { type: \"spring\", stiffness: 520, damping: 34 } as const;\n\nfunction RollingNumber({ value, reduce }: { value: number; reduce: boolean | null }) {\n  return (\n    <span className=\"relative inline-flex overflow-hidden tabular-nums\">\n      <AnimatePresence initial={false} mode=\"popLayout\">\n        <motion.span\n          animate={{ y: 0, opacity: 1 }}\n          exit={reduce ? { opacity: 0 } : { y: \"-100%\", opacity: 0 }}\n          initial={reduce ? { opacity: 0 } : { y: \"100%\", opacity: 0 }}\n          key={value}\n          transition={{ duration: 0.2, ease: [0.23, 1, 0.32, 1] }}\n        >\n          {value}\n        </motion.span>\n      </AnimatePresence>\n    </span>\n  );\n}\n\nexport function PresenceBar({\n  people,\n  title = \"Q3 roadmap\",\n  subtitle = \"Edited just now\",\n  max = 4,\n  following: followingProp,\n  onFollowChange,\n  children,\n  className,\n}: PresenceBarProps) {\n  const reduce = useSafeReducedMotion();\n  const [followingState, setFollowingState] = useState<string | null>(null);\n  const followingId = followingProp !== undefined ? followingProp : followingState;\n  const followed = people.find((p) => p.id === followingId) ?? null;\n\n  const setFollowing = (id: string | null) => {\n    if (followingProp === undefined) setFollowingState(id);\n    onFollowChange?.(id);\n  };\n\n  const shown = people.slice(0, max);\n  const rest = people.length - shown.length;\n  const hue = followed ? presenceHues[followed.hue] : null;\n\n  return (\n    <section\n      aria-label={title}\n      className={cn(\"relative w-full max-w-[480px] rounded-2xl border bg-card text-card-foreground shadow-xs/5\", className)}\n      onKeyDown={(e) => e.key === \"Escape\" && followed && setFollowing(null)}\n    >\n      <header className=\"flex items-center gap-3 px-4 py-3\">\n        <div className=\"min-w-0 flex-1\">\n          <h3 className=\"truncate font-medium text-sm\">{title}</h3>\n          <div className=\"relative h-4 text-muted-foreground text-xs\">\n            <AnimatePresence initial={false} mode=\"popLayout\">\n              {followed ? (\n                <motion.span\n                  animate={{ opacity: 1, y: 0 }}\n                  className=\"flex items-center gap-1\"\n                  exit={{ opacity: 0, y: reduce ? 0 : -4 }}\n                  initial={{ opacity: 0, y: reduce ? 0 : 4 }}\n                  key=\"following\"\n                  transition={{ duration: 0.18 }}\n                >\n                  <EyeIcon className=\"size-3\" />\n                  Following {followed.name.split(\" \")[0]}\n                  {followed.location && <span className=\"text-muted-foreground/72\">· {followed.location}</span>}\n                </motion.span>\n              ) : (\n                <motion.span\n                  animate={{ opacity: 1, y: 0 }}\n                  className=\"block\"\n                  exit={{ opacity: 0, y: reduce ? 0 : -4 }}\n                  initial={{ opacity: 0, y: reduce ? 0 : 4 }}\n                  key=\"subtitle\"\n                  transition={{ duration: 0.18 }}\n                >\n                  {subtitle}\n                </motion.span>\n              )}\n            </AnimatePresence>\n          </div>\n        </div>\n\n        <TooltipProvider delay={400}>\n          <div aria-label={`${people.length} people here`} className=\"flex items-center\" role=\"group\">\n            <AnimatePresence initial={false} mode=\"popLayout\">\n              {shown.map((p, i) => {\n                const isFollowed = p.id === followingId;\n                return (\n                  <motion.div\n                    animate={{ opacity: 1, scale: 1, x: 0 }}\n                    className={cn(\"relative\", i > 0 && \"-ms-2.5\")}\n                    exit={{ opacity: 0, scale: 0.8, transition: { duration: 0.14 } }}\n                    initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.8, x: 8 }}\n                    key={p.id}\n                    layout\n                    style={{ zIndex: isFollowed ? 20 : shown.length - i }}\n                    transition={reduce ? { duration: 0 } : spring}\n                  >\n                    <Tooltip>\n                      <TooltipTrigger\n                        render={\n                          <button\n                            aria-label={isFollowed ? `Stop following ${p.name}` : `Follow ${p.name}`}\n                            aria-pressed={isFollowed}\n                            className=\"group/face relative block cursor-pointer rounded-full bg-card p-[2px] outline-none transition-transform duration-150 ease-out focus-visible:ring-2 focus-visible:ring-ring active:scale-[0.97] [@media(hover:hover)]:hover:-translate-y-0.5\"\n                            onClick={() => setFollowing(isFollowed ? null : p.id)}\n                            type=\"button\"\n                          />\n                        }\n                      >\n                        <span\n                          className={cn(\n                            \"block rounded-full border-[1.5px] p-[1.5px] transition-colors duration-200\",\n                            isFollowed && hue\n                              ? hue.frame\n                              : p.status === \"idle\"\n                                ? \"border-warning\"\n                                : \"border-success\",\n                          )}\n                        >\n                          <span\n                            className={cn(\n                              \"flex size-7 select-none items-center justify-center rounded-full font-medium text-[10px] transition-opacity\",\n                              presenceHues[p.hue].avatar,\n                              p.status === \"idle\" && !isFollowed && \"opacity-60\",\n                            )}\n                          >\n                            {p.initials}\n                          </span>\n                        </span>\n                      </TooltipTrigger>\n                      <TooltipPopup>\n                        <span className=\"font-medium\">{p.name}</span>\n                        <span className=\"text-muted-foreground\">\n                          {\" \"}\n                          · {p.status === \"idle\" ? \"Idle\" : \"Active\"}\n                          {p.location ? ` in ${p.location}` : \"\"}\n                        </span>\n                      </TooltipPopup>\n                    </Tooltip>\n                  </motion.div>\n                );\n              })}\n              {rest > 0 && (\n                <motion.div\n                  animate={{ opacity: 1, scale: 1 }}\n                  className=\"relative -ms-2.5\"\n                  exit={{ opacity: 0, scale: 0.8 }}\n                  initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.8 }}\n                  key=\"rest\"\n                  layout\n                  transition={reduce ? { duration: 0 } : spring}\n                >\n                  <span className=\"block rounded-full bg-card p-[2px]\">\n                    <span className=\"flex size-[34px] items-center justify-center rounded-full bg-muted font-medium text-[10px] text-muted-foreground tabular-nums ring-1 ring-border ring-inset\">\n                      +{rest}\n                    </span>\n                  </span>\n                </motion.div>\n              )}\n            </AnimatePresence>\n          </div>\n        </TooltipProvider>\n\n        <div className=\"hidden w-[68px] shrink-0 items-center gap-1 text-muted-foreground text-xs min-[400px]:flex\" aria-live=\"polite\">\n          <span aria-hidden className=\"size-1.5 rounded-full bg-success\" />\n          <span>\n            <RollingNumber reduce={reduce} value={people.length} /> viewing\n          </span>\n        </div>\n      </header>\n\n      {children !== undefined && (\n        <div className=\"relative border-t\">\n          <div className=\"relative overflow-hidden rounded-b-2xl\">\n            {typeof children === \"function\" ? children(followed) : children}\n          </div>\n          <AnimatePresence>\n            {followed && hue && (\n              <motion.div\n                animate={{ opacity: 1 }}\n                className={cn(\"pointer-events-none absolute -inset-px rounded-b-2xl border-2\", hue.frame)}\n                exit={{ opacity: 0 }}\n                initial={{ opacity: 0 }}\n                key={followed.id}\n                transition={{ duration: 0.2 }}\n              >\n                <motion.div\n                  animate={{ opacity: 1, y: 0 }}\n                  className=\"pointer-events-auto absolute -top-px right-3 flex items-center\"\n                  initial={reduce ? { opacity: 0 } : { opacity: 0, y: -4 }}\n                  transition={{ duration: 0.2, ease: [0.23, 1, 0.32, 1] }}\n                >\n                  <span className={cn(\"flex h-6 items-center gap-1 rounded-b-lg ps-2 pe-1 font-medium text-xs\", hue.flag)}>\n                    {followed.name.split(\" \")[0]}'s view\n                    <button\n                      aria-label=\"Stop following\"\n                      className=\"relative flex size-5 cursor-pointer items-center justify-center rounded-md outline-none transition-colors before:absolute before:-inset-1.5 hover:bg-white/20 focus-visible:ring-2 focus-visible:ring-white\"\n                      onClick={() => setFollowing(null)}\n                      type=\"button\"\n                    >\n                      <XIcon className=\"size-3\" />\n                    </button>\n                  </span>\n                </motion.div>\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </div>\n      )}\n    </section>\n  );\n}\n"
    }
  ]
}