{
  "$schema": "https://blode.co/ui/schema/registry-item.json",
  "name": "bulk-action-bar",
  "title": "Bulk Action Bar",
  "author": "Matthew Blode",
  "description": "A sticky bar that surfaces actions for the currently selected rows.",
  "dependencies": ["blode-icons-react"],
  "registryDependencies": ["button"],
  "files": [
    {
      "path": "ui/bulk-action-bar.tsx",
      "content": "\"use client\";\n\nimport { CrossLargeIcon } from \"blode-icons-react\";\nimport { useEffect } from \"react\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\n\nexport interface BulkActionBarProps extends Omit<React.ComponentProps<\"section\">, \"children\"> {\n  /** Accessible name for the bar itself (default: \"Bulk actions\") */\n  \"aria-label\"?: string;\n  /** The actions offered for the selection */\n  children: React.ReactNode;\n  /** Accessible label for the clear button (default: \"Clear selection\") */\n  clearLabel?: string;\n  /** How many rows are selected. The bar is hidden at zero. */\n  count: number;\n  /** Renders the count line. Defaults to \"N selected\". */\n  label?: (count: number) => React.ReactNode;\n  /** Called when the selection is cleared, by the button or by Escape */\n  onClear: () => void;\n}\n\nconst BulkActionBar = ({\n  \"aria-label\": ariaLabel = \"Bulk actions\",\n  children,\n  className,\n  clearLabel = \"Clear selection\",\n  count,\n  label,\n  onClear,\n  ...props\n}: BulkActionBarProps) => {\n  const message = label?.(count) ?? (\n    <>\n      <span className=\"font-medium text-foreground tabular-figures\">{count}</span> selected\n    </>\n  );\n\n  useEffect(() => {\n    if (count === 0) {\n      return;\n    }\n\n    const handleKeyDown = (event: KeyboardEvent) => {\n      // Anything nested that handles Escape itself — a dialog, a combobox — calls\n      // preventDefault first, so the selection survives closing it.\n      if (event.key === \"Escape\" && !event.defaultPrevented) {\n        onClear();\n      }\n    };\n\n    document.addEventListener(\"keydown\", handleKeyDown);\n    return () => document.removeEventListener(\"keydown\", handleKeyDown);\n  }, [count, onClear]);\n\n  return (\n    <>\n      {/* The live region outlives the bar: a region mounted at the same moment\n          as its first message is not reliably announced. */}\n      <output className=\"sr-only\" data-slot=\"bulk-action-bar-status\">\n        {count > 0 ? message : null}\n      </output>\n\n      {count > 0 ? (\n        <section\n          aria-label={ariaLabel}\n          className={cn(\n            // Out of flow, so checking the first row does not shove the list down\n            // under the cursor. `absolute` anchors to the list's own positioned\n            // container; pass `fixed` to pin it to the viewport instead.\n            // `w-max` matters: with `left-1/2` and no `right`, shrink-to-fit sizes the\n            // bar to half the container and the actions wrap for no visible reason.\n            \"-translate-x-1/2 absolute bottom-4 left-1/2 z-20 w-max max-w-[calc(100%-2rem)]\",\n            \"flex items-center gap-2 rounded-xl bg-popover p-1.5 pl-3\",\n            // A ring rather than a border: `shadow-soft` all but disappears in\n            // dark mode, and the ring is what carries the edge there.\n            \"text-popover-foreground shadow-soft ring-1 ring-foreground/10\",\n            \"fade-in-0 slide-in-from-bottom-2 animate-in duration-150\",\n            className,\n          )}\n          data-slot=\"bulk-action-bar\"\n          {...props}\n        >\n          <span\n            aria-hidden=\"true\"\n            className=\"whitespace-nowrap text-muted-foreground text-sm\"\n            data-slot=\"bulk-action-bar-count\"\n          >\n            {message}\n          </span>\n\n          <div className=\"flex flex-wrap items-center gap-1\" data-slot=\"bulk-action-bar-actions\">\n            {children}\n          </div>\n\n          <Button aria-label={clearLabel} onClick={onClear} size=\"icon-sm\" variant=\"ghost\">\n            <CrossLargeIcon />\n          </Button>\n        </section>\n      ) : null}\n    </>\n  );\n};\n\nexport { BulkActionBar };\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}
