{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bottom-activity-strip",
  "type": "registry:component",
  "title": "Bottom Activity Strip",
  "description": "Slim horizontally-scrolling row of recent canvas events for low-noise live activity.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/bottom-activity-strip/bottom-activity-strip.tsx",
      "content": "\"use client\";\n\nimport {\n  type ComponentPropsWithoutRef,\n  forwardRef,\n  type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/**\n * Tone of an event — drives the leading dot color.\n *\n * @public\n */\nexport type ActivityStripTone =\n  | \"danger\"\n  | \"info\"\n  | \"neutral\"\n  | \"success\"\n  | \"warn\";\n\nconst TONE_DOT: Record<ActivityStripTone, string> = {\n  danger: \"bg-red-500\",\n  info: \"bg-blue-500\",\n  neutral: \"bg-muted-foreground\",\n  success: \"bg-emerald-500\",\n  warn: \"bg-amber-500\",\n};\n\n/**\n * One event in the strip.\n *\n * @public\n */\nexport type ActivityEvent = {\n  /** Stable identifier — used as the React key + analytics hook. */\n  id: string;\n  /** Short label (e.g. `\"deploy completed\"`). */\n  label: ReactNode;\n  /** Optional click handler — when provided, the chip becomes a button. */\n  onActivate?: () => void;\n  /** Optional tone for the leading dot. Defaults to `\"neutral\"`. */\n  tone?: ActivityStripTone;\n  /** Pre-formatted timestamp (host owns formatting). */\n  ts: ReactNode;\n};\n\n/**\n * Localizable strings.\n *\n * @public\n */\nexport type BottomActivityStripLabels = {\n  /** Empty-state copy. Defaults to `\"No recent activity\"`. */\n  empty?: string;\n  /** Aria-label for the strip. Defaults to `\"Recent activity\"`. */\n  region?: string;\n};\n\nconst DEFAULT_LABELS = {\n  empty: \"No recent activity\",\n  region: \"Recent activity\",\n} as const satisfies Required<BottomActivityStripLabels>;\n\n/**\n * Props for {@link BottomActivityStrip}.\n *\n * @public\n */\nexport type BottomActivityStripProps = {\n  /** Event entries — newest first. */\n  events: ActivityEvent[];\n  /** Localizable strings. */\n  labels?: BottomActivityStripLabels;\n  /** Cap the rendered events. The component drops the tail without warning. */\n  maxEvents?: number;\n} & ComponentPropsWithoutRef<\"section\">;\n\nconst ChipBody = (props: { event: ActivityEvent }): React.ReactElement => {\n  const { event } = props;\n  const tone = event.tone ?? \"neutral\";\n  return (\n    <span className=\"flex items-center gap-1.5 whitespace-nowrap\">\n      <span\n        aria-hidden=\"true\"\n        className={cn(\"size-1.5 rounded-full\", TONE_DOT[tone])}\n      />\n      <span className=\"text-foreground\">{event.label}</span>\n      <span className=\"text-[10px] text-muted-foreground\" data-strip-event-ts>\n        {event.ts}\n      </span>\n    </span>\n  );\n};\n\nconst Chip = (props: { event: ActivityEvent }): React.ReactElement => {\n  const { event } = props;\n  const tone = event.tone ?? \"neutral\";\n  if (event.onActivate) {\n    const handleClick = (): void => {\n      event.onActivate?.();\n    };\n    return (\n      <button\n        className=\"flex items-center rounded-full border border-border bg-background px-2 py-1 text-[11px] transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n        data-strip-event={event.id}\n        data-strip-event-tone={tone}\n        onClick={handleClick}\n        type=\"button\"\n      >\n        <ChipBody event={event} />\n      </button>\n    );\n  }\n  return (\n    <span\n      className=\"flex items-center rounded-full border border-border bg-background px-2 py-1 text-[11px]\"\n      data-strip-event={event.id}\n      data-strip-event-tone={tone}\n    >\n      <ChipBody event={event} />\n    </span>\n  );\n};\n\n/**\n * Slim bottom strip showing a horizontally-scrolling row of recent\n * canvas events. The lowest-noise live execution surface — keep\n * `ObjectCard`, panels, and overlays for high-value surfaces; let the\n * strip carry the steady drip of activity.\n *\n * Pure presentation; the host computes the event list (newest first)\n * and supplies an optional `onActivate` per event to jump to the\n * related object.\n *\n * Distinct from `ActivityLog` (vertical, persistent) and `LiveFeed`\n * (full-height feed): this primitive is a single horizontal row that\n * lives at the bottom of the canvas.\n *\n * @example\n * ```tsx\n * <BottomActivityStrip\n *   events={[\n *     { id: \"1\", label: \"deploy ok\", ts: \"12s\", tone: \"success\" },\n *     { id: \"2\", label: \"queue spike\", ts: \"1m\", tone: \"warn\" },\n *   ]}\n *   maxEvents={20}\n * />\n * ```\n *\n * @public\n */\nexport const BottomActivityStrip = forwardRef<\n  HTMLElement,\n  BottomActivityStripProps\n>((props, ref) => {\n  const { className, events, labels, maxEvents, ...rest } = props;\n  const resolvedLabels = { ...DEFAULT_LABELS, ...labels };\n  const visible =\n    maxEvents === undefined || maxEvents >= events.length\n      ? events\n      : events.slice(0, maxEvents);\n  return (\n    <section\n      aria-label={resolvedLabels.region}\n      className={cn(\n        \"flex w-full items-center gap-2 overflow-x-auto rounded-md border border-border bg-background/90 px-2 py-1 text-foreground\",\n        className,\n      )}\n      data-bottom-activity-strip\n      ref={ref}\n      {...rest}\n    >\n      {visible.length === 0 ? (\n        <span\n          className=\"px-2 text-[11px] text-muted-foreground\"\n          data-strip-state=\"empty\"\n        >\n          {resolvedLabels.empty}\n        </span>\n      ) : (\n        visible.map((event) => <Chip event={event} key={event.id} />)\n      )}\n    </section>\n  );\n});\nBottomActivityStrip.displayName = \"BottomActivityStrip\";\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
