{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "activity-feed",
  "title": "Activity Feed",
  "description": "Vertical feed of events with squircle markers.",
  "dependencies": [
    "class-variance-authority@^0.7.1"
  ],
  "registryDependencies": [
    "@pestoui/style",
    "@pestoui/utils"
  ],
  "files": [
    {
      "path": "pestoui/components/activity-feed.tsx",
      "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Timeline marker — a squircle, so the radius is explicit: the radius scale would\n   overrun half of a 36px box and collapse the tile into a circle. The tone tints\n   the marker only; the copy beside it always stays neutral. */\nconst activityFeedMarkerVariants = cva(\n  \"flex size-9 shrink-0 items-center justify-center rounded-[11px] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4.5\",\n  {\n    variants: {\n      tone: {\n        default:\n          \"bg-secondary text-muted-foreground ring-1 ring-foreground/[0.04]\",\n        info: \"bg-info/10 text-info\",\n        success: \"bg-success/10 text-success\",\n        warning: \"bg-warning/15 text-warning-ink\",\n        destructive:\n          \"bg-destructive/10 text-destructive dark:bg-destructive/20\",\n      },\n    },\n    defaultVariants: {\n      tone: \"default\",\n    },\n  }\n)\n\n/* A feed is a sequence, so it arrives as one: six pixels of rise and a fade per\n   item, each a beat behind the one above it. Deliberately no blur — these rows\n   are the most repeated element in the component, and a materialise cue on\n   every one of them reads as fog rather than as arrival.\n\n   `fill-mode-backwards` is load-bearing with a stagger: without it an item sits\n   at its final opacity through its own delay and then flashes back to the start\n   frame when its turn comes. */\nconst activityFeedItemEnter =\n  \"animate-in fade-in-0 slide-in-from-bottom-[6px] fill-mode-backwards anim-spring-swap ease-spring-swap\"\n\n/* Delays live on the parent so an item needs no index prop and a consumer can\n   keep composing these by hand. Capped at six beats — past that, the tail of a\n   long feed would be waiting on a cascade nobody is still watching, and the\n   `n+7` rule hands everything after the cap the last delay rather than none. */\nconst activityFeedStagger = [\n  \"[&>*:nth-child(2)]:[--tw-animation-delay:calc(1*var(--stagger-base))]\",\n  \"[&>*:nth-child(3)]:[--tw-animation-delay:calc(2*var(--stagger-base))]\",\n  \"[&>*:nth-child(4)]:[--tw-animation-delay:calc(3*var(--stagger-base))]\",\n  \"[&>*:nth-child(5)]:[--tw-animation-delay:calc(4*var(--stagger-base))]\",\n  \"[&>*:nth-child(6)]:[--tw-animation-delay:calc(5*var(--stagger-base))]\",\n  \"[&>*:nth-child(n+7)]:[--tw-animation-delay:calc(6*var(--stagger-base))]\",\n].join(\" \")\n\nfunction ActivityFeed({ className, ...props }: React.ComponentProps<\"ol\">) {\n  return (\n    <ol\n      data-slot=\"activity-feed\"\n      /* Preflight strips list-style, which drops list semantics in Safari —\n         restore them explicitly, same as the stepper does. */\n      role=\"list\"\n      className={cn(\"flex w-full flex-col\", activityFeedStagger, className)}\n      {...props}\n    />\n  )\n}\n\nfunction ActivityFeedItem({\n  icon,\n  avatar,\n  title,\n  description,\n  timestamp,\n  tone = \"default\",\n  className,\n  ...props\n}: Omit<React.ComponentProps<\"li\">, \"title\"> &\n  VariantProps<typeof activityFeedMarkerVariants> & {\n    icon?: React.ReactNode\n    avatar?: React.ReactNode\n    title: React.ReactNode\n    description?: React.ReactNode\n    timestamp?: string\n  }) {\n  return (\n    <li\n      data-slot=\"activity-feed-item\"\n      data-tone={tone}\n      className={cn(\n        \"group/feed-item flex min-h-18 gap-3.5 last:min-h-0\",\n        activityFeedItemEnter,\n        className\n      )}\n      {...props}\n    >\n      {/* Marker column. The connector is a flex child that eats the leftover\n          height, so it always runs from this marker down to the next one — and\n          the last item, having no next marker, renders no line at all. */}\n      <div className=\"flex flex-col items-center self-stretch\">\n        {avatar ? (\n          <span\n            data-slot=\"activity-feed-avatar\"\n            className=\"flex size-9 shrink-0 items-center justify-center *:data-[slot=avatar]:size-9\"\n          >\n            {avatar}\n          </span>\n        ) : (\n          <span\n            data-slot=\"activity-feed-marker\"\n            aria-hidden=\"true\"\n            className={cn(activityFeedMarkerVariants({ tone }))}\n          >\n            {icon}\n          </span>\n        )}\n        <span\n          data-slot=\"activity-feed-connector\"\n          aria-hidden=\"true\"\n          className=\"my-2 w-px flex-1 border-l border-border group-last/feed-item:hidden\"\n        />\n      </div>\n\n      <div className=\"min-w-0 flex-1 pb-7 group-last/feed-item:pb-0\">\n        <div className=\"flex items-start gap-3\">\n          <div\n            data-slot=\"activity-feed-title\"\n            className=\"min-w-0 flex-1 text-body leading-6 font-medium tracking-snug\"\n          >\n            {title}\n          </div>\n          {timestamp && (\n            <span\n              data-slot=\"activity-feed-timestamp\"\n              className=\"shrink-0 text-caption leading-6 whitespace-nowrap text-muted-foreground tabular-nums\"\n            >\n              {timestamp}\n            </span>\n          )}\n        </div>\n        {description && (\n          <div\n            data-slot=\"activity-feed-description\"\n            className=\"mt-1 text-body leading-normal text-muted-foreground\"\n          >\n            {description}\n          </div>\n        )}\n      </div>\n    </li>\n  )\n}\n\nexport { ActivityFeed, ActivityFeedItem, activityFeedMarkerVariants }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}