{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table-toolbar",
  "title": "Data Table Toolbar",
  "description": "Search, filters, column visibility, export, and bulk actions for selected rows.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "checkbox",
    "input",
    "label",
    "popover",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/data-table-toolbar.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { Columns3, Download, Search, X } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type ReactNode,\n  useContext,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const DATA_TABLE_TOOLBAR_VARIANTS = [\"toolbar\", \"stacked\", \"compact\"] as const;\nexport type DataTableToolbarVariant = (typeof DATA_TABLE_TOOLBAR_VARIANTS)[number];\nexport type DataTableToolbarProps = ComponentProps<\"div\"> & {\n  variant?: DataTableToolbarVariant;\n  search?: string;\n  defaultSearch?: string;\n  onSearchChange?: (search: string) => void;\n  selectedCount?: number;\n  onClearSelection?: () => void;\n};\n\ntype ToolbarContext = {\n  id: string;\n  variant: DataTableToolbarVariant;\n  search: string;\n  setSearch: (search: string) => void;\n  selectedCount: number;\n  onClearSelection?: () => void;\n};\nconst Context = createContext<ToolbarContext | null>(null);\nfunction useToolbar(part: string) {\n  const context = useContext(Context);\n  if (!context) throw new Error(`${part} must be used within DataTableToolbar`);\n  return context;\n}\n\ntype ColumnsContext = { value: string[]; toggle: (column: string, visible: boolean) => void };\nconst ColumnsCtx = createContext<ColumnsContext | null>(null);\n\nconst easeOut = \"cubic-bezier(0.23, 1, 0.32, 1)\";\n// Secondary pill on the shadcn Button: raised fill, lighter on hover, a small press.\nconst controlVariants = cva(\n  \"inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-full border-0 bg-secondary py-0 font-medium whitespace-nowrap text-foreground transition-[background-color,scale] duration-[120ms,140ms] ease-[ease-out,cubic-bezier(0.23,1,0.32,1)] hover:bg-[color-mix(in_oklab,var(--secondary)_85%,var(--foreground))] focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid active:scale-[0.97] disabled:pointer-events-none disabled:opacity-50 aria-expanded:bg-[color-mix(in_oklab,var(--secondary)_85%,var(--foreground))] motion-reduce:transition-none motion-reduce:active:scale-100 [&_svg:not([class*='size-'])]:size-3.5 [&>svg]:text-muted-foreground\",\n  {\n    variants: {\n      variant: {\n        toolbar: \"h-7 px-3 text-[12.5px]/[18px] has-[>svg]:px-3\",\n        stacked: \"h-7 px-3 text-[12.5px]/[18px] has-[>svg]:px-3\",\n        compact: \"h-6 px-2.5 text-[12px]/[18px] has-[>svg]:px-2.5\",\n      },\n    },\n  },\n);\n\nconst dataTableToolbarVariants = cva(\"flex min-w-0 text-[13px]/[18px] text-foreground\", {\n  variants: {\n    variant: {\n      toolbar: \"flex-row flex-wrap items-center gap-2 rounded-[14px] border bg-card p-2\",\n      stacked:\n        \"flex-col flex-nowrap items-stretch gap-2 rounded-[14px] border-0 bg-background p-2.5 inset-ring-1 inset-ring-border\",\n      compact: \"flex-row flex-wrap items-center gap-1.5 rounded-xl border bg-card p-1.5\",\n    },\n  },\n});\n\nexport function DataTableToolbar({\n  variant = \"toolbar\",\n  search,\n  defaultSearch = \"\",\n  onSearchChange,\n  selectedCount = 0,\n  onClearSelection,\n  className,\n  children,\n  ...props\n}: DataTableToolbarProps) {\n  const id = useId();\n  const [internal, setInternal] = useState(defaultSearch);\n  const context: ToolbarContext = {\n    id,\n    variant,\n    search: search ?? internal,\n    setSearch: (next) => {\n      if (search === undefined) setInternal(next);\n      onSearchChange?.(next);\n    },\n    selectedCount,\n    onClearSelection,\n  };\n  return (\n    <Context.Provider value={context}>\n      {/* biome-ignore lint/a11y/useSemanticElements: the toolbar groups mixed table controls, not one form fieldset. */}\n      <div\n        role=\"group\"\n        aria-label=\"Table controls\"\n        data-slot=\"data-table-toolbar\"\n        data-variant={variant}\n        className={cn(dataTableToolbarVariants({ variant }), className)}\n        {...props}\n      >\n        {children}\n        <span role=\"status\" className=\"sr-only\">\n          {selectedCount > 0 ? `${selectedCount} selected` : \"\"}\n        </span>\n      </div>\n    </Context.Provider>\n  );\n}\n\nexport function DataTableToolbarGroup({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"data-table-toolbar-group\"\n      className={cn(\"flex min-w-0 flex-wrap items-center gap-1.5\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function DataTableToolbarSearch({\n  label = \"Search rows\",\n  className,\n  onChange,\n  onKeyDown,\n  ...props\n}: Omit<ComponentProps<\"input\">, \"value\" | \"defaultValue\" | \"type\"> & { label?: string }) {\n  const context = useToolbar(\"DataTableToolbarSearch\");\n  const inputRef = useRef<HTMLInputElement>(null);\n  const compact = context.variant === \"compact\";\n  return (\n    <div\n      data-slot=\"data-table-toolbar-search\"\n      className={cn(\n        \"flex min-w-0 items-center gap-1.5 rounded-full border border-transparent [transition:border-color_120ms_ease-out,background-color_120ms_ease-out] focus-within:border-border-strong motion-reduce:transition-none\",\n        compact ? \"h-6 pr-0.75 pl-[9px]\" : \"pr-1 pl-[11px]\",\n        context.variant === \"stacked\"\n          ? \"h-8 flex-[0_0_auto] bg-card\"\n          : cn(\"flex-[1_1_200px] bg-muted\", !compact && \"h-7\"),\n      )}\n    >\n      <Search\n        size={14}\n        strokeWidth={1.75}\n        aria-hidden=\"true\"\n        className=\"shrink-0 text-subtle-foreground size-3.5\"\n      />\n      <Input\n        aria-label={label}\n        data-slot=\"data-table-toolbar-search-input\"\n        className={cn(\n          \"h-auto w-full min-w-0 flex-1 rounded-none border-0 bg-transparent p-0 text-inherit shadow-none outline-none placeholder:text-subtle-foreground focus-visible:ring-0 dark:bg-transparent [&::-webkit-search-cancel-button]:hidden\",\n          compact\n            ? \"text-[12px]/[18px] md:text-[12px]/[18px]\"\n            : \"text-[12.5px]/[18px] md:text-[12.5px]/[18px]\",\n          className,\n        )}\n        {...props}\n        ref={inputRef}\n        type=\"search\"\n        id={`${context.id}-search`}\n        value={context.search}\n        onChange={(event) => {\n          onChange?.(event);\n          if (!event.defaultPrevented) context.setSearch(event.target.value);\n        }}\n        onKeyDown={(event) => {\n          onKeyDown?.(event);\n          if (!event.defaultPrevented && event.key === \"Escape\" && context.search) {\n            event.preventDefault();\n            context.setSearch(\"\");\n          }\n        }}\n      />\n      {context.search ? (\n        <Button\n          type=\"button\"\n          variant=\"secondary\"\n          size=\"icon\"\n          aria-label=\"Clear search\"\n          onClick={() => {\n            context.setSearch(\"\");\n            inputRef.current?.focus();\n          }}\n          className={cn(\n            controlVariants({ variant: context.variant }),\n            \"size-5 p-0 has-[>svg]:p-0 [&_svg:not([class*='size-'])]:size-3\",\n            \"animate-in fade-in-0 zoom-in-75 duration-150 ease-out-quint motion-reduce:animate-none\",\n          )}\n        >\n          <X size={12} className=\"size-3\" strokeWidth={2} aria-hidden=\"true\" />\n        </Button>\n      ) : null}\n    </div>\n  );\n}\n\nexport function DataTableToolbarButton({ className, ...props }: ComponentProps<\"button\">) {\n  const context = useToolbar(\"DataTableToolbarButton\");\n  return (\n    <Button\n      data-slot=\"data-table-toolbar-button\"\n      variant=\"secondary\"\n      className={cn(controlVariants({ variant: context.variant }), className)}\n      {...props}\n      type=\"button\"\n    />\n  );\n}\n\nexport function DataTableToolbarExport({\n  children = \"Export\",\n  ...props\n}: ComponentProps<\"button\">) {\n  return (\n    <DataTableToolbarButton data-slot=\"data-table-toolbar-export\" {...props}>\n      <Download size={14} className=\"size-3.5\" strokeWidth={1.75} aria-hidden=\"true\" />\n      {children}\n    </DataTableToolbarButton>\n  );\n}\n\nexport type DataTableToolbarColumnsProps = Omit<ComponentProps<\"div\">, \"defaultValue\"> & {\n  value?: string[];\n  defaultValue?: string[];\n  onValueChange?: (value: string[]) => void;\n  label?: string;\n};\n\nexport function DataTableToolbarColumns({\n  value,\n  defaultValue = [],\n  onValueChange,\n  label = \"Columns\",\n  children,\n  className,\n  ...props\n}: DataTableToolbarColumnsProps) {\n  const context = useToolbar(\"DataTableToolbarColumns\");\n  const [internal, setInternal] = useState(defaultValue);\n  const panelId = `${context.id}-columns`;\n  const visible = value ?? internal;\n\n  const columns: ColumnsContext = {\n    value: visible,\n    toggle: (column, show) => {\n      const next = show ? [...visible, column] : visible.filter((item) => item !== column);\n      if (value === undefined) setInternal(next);\n      onValueChange?.(next);\n    },\n  };\n\n  return (\n    <ColumnsCtx.Provider value={columns}>\n      <div data-slot=\"data-table-toolbar-columns\" className={cn(\"relative\", className)} {...props}>\n        <Popover>\n          <PopoverTrigger asChild>\n            <Button\n              type=\"button\"\n              variant=\"secondary\"\n              className={controlVariants({ variant: context.variant })}\n            >\n              <Columns3 size={14} className=\"size-3.5\" strokeWidth={1.75} aria-hidden=\"true\" />\n              {label}\n            </Button>\n          </PopoverTrigger>\n          <PopoverContent\n            id={panelId}\n            align=\"end\"\n            sideOffset={6}\n            aria-label={`Visible ${label.toLowerCase()}`}\n            className=\"w-auto min-w-[184px] rounded-[14px] border-0 bg-popover p-1 text-[13px]/[18px] text-popover-foreground shadow-[0_0_0_1px_var(--border-strong),0_16px_32px_-12px_oklch(0_0_0/0.32),0_4px_8px_-4px_oklch(0_0_0/0.12)] data-[side=bottom]:slide-in-from-top-0 data-[side=left]:slide-in-from-right-0 data-[side=right]:slide-in-from-left-0 data-[side=top]:slide-in-from-bottom-0 data-[state=open]:zoom-in-96 data-open:zoom-in-96 data-[state=open]:duration-180 data-open:duration-180 data-[state=open]:ease-[cubic-bezier(0.16,1,0.3,1)] data-open:ease-[cubic-bezier(0.16,1,0.3,1)] motion-reduce:animate-none\"\n          >\n            <fieldset className=\"m-0 grid min-w-0 gap-px border-0 p-0\">\n              <legend className=\"sr-only\">{`Visible ${label.toLowerCase()}`}</legend>\n              {children}\n            </fieldset>\n          </PopoverContent>\n        </Popover>\n      </div>\n    </ColumnsCtx.Provider>\n  );\n}\n\nexport function DataTableToolbarColumn({\n  value,\n  children,\n  disabled,\n  className,\n}: {\n  value: string;\n  children: ReactNode;\n  disabled?: boolean;\n  className?: string;\n}) {\n  const columns = useContext(ColumnsCtx);\n  if (!columns)\n    throw new Error(\"DataTableToolbarColumn must be used within DataTableToolbarColumns\");\n  return (\n    <Label\n      data-slot=\"data-table-toolbar-column\"\n      className={cn(\n        \"flex min-h-[30px] items-center gap-2 rounded-lg px-2 py-1 text-[13px] leading-[18px] font-medium select-auto\",\n        disabled\n          ? \"cursor-default opacity-45\"\n          : \"cursor-pointer [transition:background-color_120ms_ease-out] hover:bg-accent motion-reduce:transition-none\",\n        className,\n      )}\n    >\n      <Checkbox\n        disabled={disabled}\n        checked={columns.value.includes(value)}\n        onCheckedChange={(checked) => columns.toggle(value, checked === true)}\n        className=\"m-0 size-3.5 border-subtle-foreground data-[state=checked]:border-primary data-checked:border-primary shadow-none focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid disabled:cursor-default disabled:opacity-100 [&_svg]:size-3\"\n      />\n      {children}\n    </Label>\n  );\n}\n\nexport function DataTableToolbarBulkActions({\n  className,\n  children,\n  ...props\n}: ComponentProps<\"div\">) {\n  const context = useToolbar(\"DataTableToolbarBulkActions\");\n  const visible = context.selectedCount > 0;\n  const ref = useRef<HTMLDivElement>(null);\n  // Fade the bar in when a selection starts.\n  useLayoutEffect(() => {\n    const bar = ref.current;\n    if (!visible || !bar || typeof bar.animate !== \"function\") return;\n    if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n    bar.animate(\n      [\n        { opacity: 0, transform: \"translateY(-4px)\" },\n        { opacity: 1, transform: \"none\" },\n      ],\n      { duration: 220, easing: easeOut },\n    );\n  }, [visible]);\n  // Stay mounted while the exit animation plays; reduced motion has none, so unmount at once.\n  const [rendered, setRendered] = useState(visible);\n  if (visible && !rendered) setRendered(true);\n  useEffect(() => {\n    if (visible) return;\n    const bar = ref.current;\n    const animation = bar ? window.getComputedStyle(bar).animationName : \"\";\n    if (!animation || animation === \"none\") setRendered(false);\n  }, [visible]);\n  if (!visible && !rendered) return null;\n  return (\n    // biome-ignore lint/a11y/useSemanticElements: bulk actions are buttons, not a form fieldset.\n    <div\n      role=\"group\"\n      aria-label=\"Bulk actions\"\n      data-slot=\"data-table-toolbar-bulk-actions\"\n      data-state={visible ? \"open\" : \"closed\"}\n      aria-hidden={visible ? undefined : true}\n      inert={!visible}\n      className={cn(\n        \"flex min-w-0 flex-wrap items-center gap-1.5 rounded-full bg-primary/12 inset-ring-1 inset-ring-primary/22\",\n        \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-1 data-[state=closed]:duration-150 data-[state=closed]:ease-out data-[state=closed]:fill-mode-forwards motion-reduce:animate-none\",\n        context.variant === \"stacked\" ? \"basis-auto\" : \"basis-full\",\n        context.variant === \"compact\" ? \"py-0.75 pr-0.75 pl-2.5\" : \"py-1 pr-1 pl-3\",\n        className,\n      )}\n      {...props}\n      ref={ref}\n      onAnimationEnd={(event) => {\n        props.onAnimationEnd?.(event);\n        if (event.target === event.currentTarget && !visible) setRendered(false);\n      }}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport function DataTableToolbarSelectionCount({\n  className,\n  children,\n  ...props\n}: ComponentProps<\"span\">) {\n  const context = useToolbar(\"DataTableToolbarSelectionCount\");\n  return (\n    <span\n      data-slot=\"data-table-toolbar-selection-count\"\n      className={cn(\"mr-auto text-[12.5px] font-medium text-foreground tabular-nums\", className)}\n      {...props}\n      aria-hidden=\"true\"\n    >\n      {children ?? `${context.selectedCount} selected`}\n    </span>\n  );\n}\n\nexport function DataTableToolbarClearSelection({\n  children = \"Clear selection\",\n  onClick,\n  ...props\n}: ComponentProps<\"button\">) {\n  const context = useToolbar(\"DataTableToolbarClearSelection\");\n  return (\n    <DataTableToolbarButton\n      data-slot=\"data-table-toolbar-clear-selection\"\n      {...props}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) context.onClearSelection?.();\n      }}\n    >\n      {children}\n    </DataTableToolbarButton>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/data-table-toolbar.tsx"
    }
  ],
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}