{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sidebar-activity",
  "type": "registry:ui",
  "title": "SidebarActivity",
  "description": "Background-work visibility inside a sidebar: status, progress, review/resume actions and a collapsed summary with accessible expansion and focus handoff.",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/sidebar.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/sidebar-activity/sidebar-activity.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/sidebar-activity/sidebar-activity.tsx",
      "content": "import {\n  forwardRef,\n  useEffect,\n  useId,\n  useRef,\n  type HTMLAttributes,\n  type ReactNode,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useSidebarState } from \"../sidebar\";\n\nexport type SidebarActivityStatus =\n  \"running\" | \"attention\" | \"complete\" | \"queued\";\nexport type SidebarActivityAction = { label: string } & (\n  { href: string; onAction?: never } | { href?: never; onAction: () => void }\n);\nexport interface SidebarActivityItem {\n  id: string;\n  title: string;\n  status: SidebarActivityStatus;\n  description?: string;\n  /** Percentage from 0 to 100. Omit for indeterminate running work. */\n  progress?: number;\n  action?: SidebarActivityAction;\n}\nexport type SidebarActivityCounts = Record<SidebarActivityStatus, number>;\nexport interface SidebarActivityProps extends Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> {\n  items: readonly SidebarActivityItem[];\n  label?: string;\n  emptyMessage?: ReactNode;\n  openLabel?: string;\n  statusLabels?: Partial<Record<SidebarActivityStatus, string>>;\n  formatSummary?: (counts: SidebarActivityCounts) => string;\n  /** Open consumer-owned details instead of expanding, including permanent rails. */\n  onOpen?: () => void;\n}\n\nconst labels: Record<SidebarActivityStatus, string> = {\n  running: \"Running\",\n  attention: \"Needs attention\",\n  complete: \"Complete\",\n  queued: \"Queued\",\n};\nconst tones: Record<SidebarActivityStatus, string> = {\n  running: \"text-primary\",\n  attention: \"text-warning\",\n  complete: \"text-success\",\n  queued: \"text-muted-foreground\",\n};\n\nfunction ActivityIcon({ status }: { status?: SidebarActivityStatus }) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      viewBox=\"0 0 20 20\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      className=\"size-4 shrink-0\"\n    >\n      {status === \"complete\" ? (\n        <path d=\"m4 10 4 4 8-8\" />\n      ) : status === \"attention\" ? (\n        <>\n          <path d=\"M10 3 2 17h16L10 3Z\" />\n          <path d=\"M10 8v4m0 2v.5\" />\n        </>\n      ) : status === \"queued\" ? (\n        <>\n          <circle cx=\"10\" cy=\"10\" r=\"7\" />\n          <path d=\"M10 6v4l3 2\" />\n        </>\n      ) : (\n        <path d=\"M2 10h3l3-6 4 12 3-6h3\" />\n      )}\n    </svg>\n  );\n}\n\nexport const SidebarActivity = forwardRef<HTMLDivElement, SidebarActivityProps>(\n  (\n    {\n      items,\n      label = \"Activity\",\n      emptyMessage = \"All caught up\",\n      openLabel = \"Open activity\",\n      statusLabels,\n      formatSummary,\n      onOpen,\n      className,\n      ...props\n    },\n    ref,\n  ) => {\n    const { collapsed, variant, setCollapsed } = useSidebarState();\n    const headingId = useId();\n    const detailsRef = useRef<HTMLElement | null>(null);\n    const pendingFocus = useRef(false);\n    const counts: SidebarActivityCounts = {\n      running: 0,\n      attention: 0,\n      complete: 0,\n      queued: 0,\n    };\n    for (const item of items) counts[item.status]++;\n    const resolvedLabels = {\n      running: statusLabels?.running ?? labels.running,\n      attention: statusLabels?.attention ?? labels.attention,\n      complete: statusLabels?.complete ?? labels.complete,\n      queued: statusLabels?.queued ?? labels.queued,\n    };\n    const summary =\n      formatSummary?.(counts) ??\n      (Object.entries(counts)\n        .filter(([, count]) => count > 0)\n        .map(\n          ([status, count]) =>\n            `${count} ${resolvedLabels[status as SidebarActivityStatus].toLowerCase()}`,\n        )\n        .join(\", \") ||\n        \"No activity\");\n    const summaryStatus =\n      counts.attention > 0\n        ? \"attention\"\n        : counts.running > 0\n          ? \"running\"\n          : counts.queued > 0\n            ? \"queued\"\n            : \"complete\";\n\n    useEffect(() => {\n      if (!collapsed && pendingFocus.current) {\n        detailsRef.current?.focus();\n        pendingFocus.current = false;\n      }\n    }, [collapsed]);\n\n    const open = () => {\n      if (onOpen) {\n        onOpen();\n        return;\n      }\n      pendingFocus.current = true;\n      setCollapsed(false);\n    };\n    const compactContent = (\n      <>\n        <span className={tones[summaryStatus]}>\n          <ActivityIcon status={summaryStatus} />\n        </span>\n        <span className=\"text-[0.625rem] leading-none font-semibold tabular-nums\">\n          {items.length}\n        </span>\n      </>\n    );\n    const compactClass = cn(\n      \"flex aspect-square w-full flex-col items-center justify-center gap-0.5 rounded-md border border-border bg-background p-1\",\n      \"text-foreground\",\n    );\n    const actionClass =\n      \"rounded px-1 py-0.5 text-xs font-medium text-foreground underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-ring\";\n\n    return (\n      <div\n        {...props}\n        ref={ref}\n        data-slot=\"sidebar-activity\"\n        data-collapsed={collapsed ? \"true\" : \"false\"}\n        className={cn(\n          \"min-w-0 p-[var(--dt-space-2)] data-[collapsed=true]:px-0\",\n          className,\n        )}\n      >\n        <p role=\"status\" aria-atomic=\"true\" className=\"sr-only\">\n          {summary}\n        </p>\n        {collapsed ? (\n          variant !== \"rail\" || onOpen ? (\n            <button\n              type=\"button\"\n              className={cn(\n                compactClass,\n                \"hover:bg-muted focus-visible:outline-ring focus-visible:outline-2\",\n              )}\n              aria-label={`${openLabel}: ${summary}`}\n              onClick={open}\n            >\n              {compactContent}\n            </button>\n          ) : (\n            <div\n              className={compactClass}\n              role=\"img\"\n              aria-label={`${label}: ${summary}`}\n            >\n              {compactContent}\n            </div>\n          )\n        ) : (\n          <section\n            ref={detailsRef}\n            tabIndex={-1}\n            aria-labelledby={headingId}\n            className=\"border-border bg-background focus-visible:ring-ring rounded-lg border p-[var(--dt-density-gap)] outline-none focus-visible:ring-2\"\n          >\n            <div className=\"mb-3 flex items-center justify-between gap-2\">\n              <h3\n                id={headingId}\n                className=\"text-muted-foreground text-xs font-medium\"\n              >\n                {label}\n              </h3>\n              <span\n                aria-hidden=\"true\"\n                className=\"text-muted-foreground text-xs tabular-nums\"\n              >\n                {items.length}\n              </span>\n            </div>\n            {items.length === 0 ? (\n              <p className=\"text-muted-foreground text-xs\">{emptyMessage}</p>\n            ) : (\n              <ul className=\"m-0 grid list-none gap-4 p-0\">\n                {items.map((item) => (\n                  <li key={item.id} className=\"min-w-0\">\n                    <div className=\"flex items-start gap-2\">\n                      <span className={cn(\"mt-0.5\", tones[item.status])}>\n                        <ActivityIcon status={item.status} />\n                      </span>\n                      <div className=\"min-w-0 flex-1\">\n                        <p className=\"text-foreground text-xs font-medium break-words\">\n                          {item.title}\n                        </p>\n                        <p className=\"text-muted-foreground mt-0.5 text-xs\">\n                          {resolvedLabels[item.status]}\n                        </p>\n                      </div>\n                    </div>\n                    {item.description && (\n                      <p className=\"text-muted-foreground mt-1 text-xs break-words\">\n                        {item.description}\n                      </p>\n                    )}\n                    {item.status === \"running\" && (\n                      <progress\n                        aria-label={`${item.title} progress`}\n                        max={100}\n                        value={\n                          Number.isFinite(item.progress)\n                            ? Math.min(100, Math.max(0, item.progress!))\n                            : undefined\n                        }\n                        className=\"accent-primary bg-muted [&::-moz-progress-bar]:bg-primary [&::-webkit-progress-bar]:bg-muted [&::-webkit-progress-value]:bg-primary mt-2 block h-1.5 w-full appearance-none overflow-hidden rounded-full\"\n                      />\n                    )}\n                    {item.action && (\n                      <div className=\"mt-1.5 text-end\">\n                        {item.action.href !== undefined ? (\n                          <a className={actionClass} href={item.action.href}>\n                            {item.action.label}\n                          </a>\n                        ) : (\n                          <button\n                            className={actionClass}\n                            type=\"button\"\n                            onClick={item.action.onAction}\n                          >\n                            {item.action.label}\n                          </button>\n                        )}\n                      </div>\n                    )}\n                  </li>\n                ))}\n              </ul>\n            )}\n          </section>\n        )}\n      </div>\n    );\n  },\n);\nSidebarActivity.displayName = \"SidebarActivity\";\n"
    },
    {
      "path": "packages/components/src/components/sidebar-activity/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/sidebar-activity/index.ts",
      "content": "export {\n  SidebarActivity,\n  type SidebarActivityProps,\n  type SidebarActivityItem,\n  type SidebarActivityAction,\n  type SidebarActivityStatus,\n  type SidebarActivityCounts,\n} from \"./sidebar-activity\";\n"
    }
  ]
}
