{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table-toolbar",
  "type": "registry:ui",
  "title": "Table Toolbar",
  "description": "Search, filter, density, view and export in one arrow-key toolbar that reflows.",
  "categories": [
    "tables"
  ],
  "dependencies": [
    "@base-ui/react",
    "@number-flow/react",
    "motion"
  ],
  "registryDependencies": [
    "https://www.stealth.pm/r/tokens.json",
    "https://www.stealth.pm/r/cn.json",
    "https://www.stealth.pm/r/icons.json",
    "https://www.stealth.pm/r/motion.json",
    "https://www.stealth.pm/r/use-controllable-state.json"
  ],
  "files": [
    {
      "path": "components/ui/table-toolbar.tsx",
      "content": "\"use client\";\nimport { Menu } from \"@base-ui/react/menu\";\nimport { Toggle } from \"@base-ui/react/toggle\";\nimport { ToggleGroup } from \"@base-ui/react/toggle-group\";\nimport { Toolbar } from \"@base-ui/react/toolbar\";\nimport { Tooltip } from \"@base-ui/react/tooltip\";\nimport NumberFlow from \"@number-flow/react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { Fragment, useEffect, useId, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/cn\";\nimport { Check, Download, Filter, Loader, Search, X } from \"@/lib/icons\";\nimport { ease, spring } from \"@/lib/motion\";\nimport { useControllableState } from \"@/lib/use-controllable-state\";\n\n/** Row heights for each density, in px. Put them on your rows and transition `height`. */\nexport const tableDensity = { compact: 32, comfortable: 44 } as const;\nexport type TableDensity = keyof typeof tableDensity;\n\nconst focusRing = \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-fg-3\";\n\n// Secondary button look shared by Filter and Export. Below a 448px toolbar the label\n// drops to screen-reader-only and the button squares up around its icon.\nconst buttonClass = cn(\n  \"group/btn relative inline-flex h-8 shrink-0 select-none items-center justify-center gap-1.5 rounded-lg px-2.5 text-[12.5px] font-medium tracking-[-0.005em]\",\n  \"border border-line-2 bg-raised text-fg shadow-[var(--shadow)] hover:border-fg-4 hover:bg-hover data-popup-open:border-fg-4 data-popup-open:bg-hover\",\n  \"transition-[background-color,border-color,color,scale] duration-150 active:scale-[0.97] active:duration-75\",\n  \"data-disabled:pointer-events-none data-disabled:opacity-50\",\n  \"@max-md:w-8 @max-md:px-0\",\n  \"before:absolute before:-inset-1.5 before:content-[''] pointer-fine:before:hidden\",\n  focusRing,\n);\n\nconst menuPopupClass = cn(\n  \"max-h-(--available-height) min-w-52 max-w-[min(18rem,var(--available-width))] overflow-y-auto overscroll-contain rounded-xl border border-line-2 bg-raised p-1 text-fg shadow-pop outline-none\",\n  \"origin-(--transform-origin) transition-[opacity,scale,translate] duration-180 ease-out-expo\",\n  \"data-starting-style:scale-96 data-starting-style:opacity-0 data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=top]:data-starting-style:translate-y-1\",\n  \"data-ending-style:scale-98 data-ending-style:opacity-0 data-ending-style:duration-120\",\n  \"data-instant:duration-0 motion-reduce:scale-100 motion-reduce:translate-none\",\n);\n\nconst menuItemClass = cn(\n  \"flex h-8 cursor-default select-none items-center gap-2.5 rounded-lg px-2 text-[13px] outline-none\",\n  \"transition-colors duration-100 data-highlighted:bg-hover data-disabled:opacity-50\",\n);\n\n/* -------------------------------------------------------------------------------------------------\n * Root\n * -----------------------------------------------------------------------------------------------*/\n\nexport type TableToolbarProps = Omit<Toolbar.Root.Props, \"className\"> & { className?: string };\n\n/** One tab stop for the whole bar; arrow keys move between its controls. */\nexport function TableToolbar({ className, ...rest }: TableToolbarProps) {\n  return (\n    <Tooltip.Provider delay={500} timeout={400}>\n      <Toolbar.Root className={cn(\"@container flex w-full min-w-0 flex-wrap items-center gap-1.5\", className)} {...rest} />\n    </Tooltip.Provider>\n  );\n}\n\nexport function TableToolbarSeparator({ className, ...rest }: Omit<Toolbar.Separator.Props, \"className\"> & { className?: string }) {\n  return <Toolbar.Separator className={cn(\"mx-0.5 h-4 w-px shrink-0 bg-line-2\", className)} {...rest} />;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Search\n * -----------------------------------------------------------------------------------------------*/\n\nexport type TableToolbarSearchProps = Omit<React.ComponentProps<\"input\">, \"value\" | \"defaultValue\" | \"onChange\" | \"size\"> & {\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  /** A key that focuses the field from anywhere on the page. Pass null to turn it off. */\n  shortcut?: string | null;\n  /** Results are being fetched: the glass becomes a spinner in the same spot. */\n  busy?: boolean;\n};\n\nexport function TableToolbarSearch({\n  value: valueProp,\n  defaultValue = \"\",\n  onValueChange,\n  shortcut = \"/\",\n  busy = false,\n  placeholder = \"Search\",\n  className,\n  ...rest\n}: TableToolbarSearchProps) {\n  const [value, setValue] = useControllableState({ value: valueProp, defaultValue, onChange: onValueChange });\n  const inputRef = useRef<HTMLInputElement>(null);\n  const reduce = useReducedMotion();\n\n  useEffect(() => {\n    if (!shortcut) return;\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key !== shortcut || e.metaKey || e.ctrlKey || e.altKey || e.defaultPrevented) return;\n      const t = e.target as HTMLElement | null;\n      // Single-key shortcuts never fire while someone is typing somewhere else.\n      if (t && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))) return;\n      // Consumed: nothing further up the page should also act on this key.\n      e.preventDefault();\n      e.stopPropagation();\n      inputRef.current?.focus();\n      inputRef.current?.select();\n    };\n    document.addEventListener(\"keydown\", onKey);\n    return () => document.removeEventListener(\"keydown\", onKey);\n  }, [shortcut]);\n\n  return (\n    <div\n      data-busy={busy || undefined}\n      className={cn(\n        // Under 448px the field takes its own row so it stays wide enough to type in.\n        \"relative flex h-8 min-w-0 flex-1 items-center rounded-lg border @max-md:basis-full border-line-2 bg-raised shadow-[var(--shadow)]\",\n        \"transition-[border-color,box-shadow] duration-150 hover:border-fg-4 focus-within:border-fg-4 focus-within:ring-2 focus-within:ring-fg/10\",\n        className,\n      )}\n    >\n      <span className=\"pointer-events-none relative grid size-4 shrink-0 place-items-center pl-2.5 text-fg-4 box-content\">\n        <AnimatePresence initial={false}>\n          <motion.span\n            key={busy ? \"busy\" : \"idle\"}\n            className=\"absolute inset-0 grid place-items-center\"\n            initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.6 }}\n            animate={{ opacity: 1, scale: 1 }}\n            exit={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.6 }}\n            transition={spring.pop}\n          >\n            {busy ? <Loader size={14} className=\"animate-spin-slow text-fg-3\" /> : <Search size={14} />}\n          </motion.span>\n        </AnimatePresence>\n      </span>\n      <Toolbar.Input\n        ref={inputRef}\n        type=\"search\"\n        value={value}\n        onChange={(e) => setValue(e.target.value)}\n        onKeyDown={(e) => {\n          // Escape empties the field first; with nothing to clear it leaves focus alone for the page to handle.\n          if (e.key === \"Escape\" && value) {\n            e.preventDefault();\n            setValue(\"\");\n          }\n        }}\n        placeholder={placeholder}\n        aria-label={rest[\"aria-label\"] ?? placeholder}\n        aria-busy={busy || undefined}\n        autoComplete=\"off\"\n        spellCheck={false}\n        enterKeyHint=\"search\"\n        className=\"peer h-full min-w-0 flex-1 bg-transparent pl-2 pr-2 text-base text-fg outline-none placeholder:text-fg-4 sm:text-[13px] [&::-webkit-search-cancel-button]:hidden\"\n        {...rest}\n      />\n      {value ? (\n        <button\n          type=\"button\"\n          tabIndex={-1}\n          aria-label=\"Clear search\"\n          onClick={() => {\n            setValue(\"\");\n            inputRef.current?.focus();\n          }}\n          className=\"relative mr-1.5 grid size-5 shrink-0 place-items-center rounded-md text-fg-3 outline-none transition-[color,background-color,scale] duration-150 hover:bg-hover hover:text-fg active:scale-[0.9] before:absolute before:-inset-3 before:content-[''] pointer-fine:before:hidden\"\n        >\n          <X size={12} />\n        </button>\n      ) : (\n        shortcut && (\n          <kbd\n            aria-hidden\n            className=\"pointer-events-none mr-1.5 grid h-5 min-w-5 place-items-center rounded-[5px] border border-line-2 px-1 font-mono text-[11px] text-fg-3 transition-opacity duration-150 peer-focus:opacity-0 pointer-coarse:hidden\"\n          >\n            {shortcut}\n          </kbd>\n        )\n      )}\n    </div>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Filter\n * -----------------------------------------------------------------------------------------------*/\n\nexport type TableToolbarFilterOption = { value: string; label: string; group?: string; count?: number };\n\nexport type TableToolbarFilterProps = {\n  options: TableToolbarFilterOption[];\n  value?: string[];\n  defaultValue?: string[];\n  onValueChange?: (value: string[]) => void;\n  label?: string;\n  className?: string;\n};\n\nexport function TableToolbarFilter({ options, value: valueProp, defaultValue = [], onValueChange, label = \"Filter\", className }: TableToolbarFilterProps) {\n  const [value, setValue] = useControllableState({ value: valueProp, defaultValue, onChange: onValueChange });\n  const reduce = useReducedMotion();\n  const active = value.length;\n\n  // Consecutive options with the same group share a heading.\n  const groups: { name?: string; options: TableToolbarFilterOption[] }[] = [];\n  for (const o of options) {\n    const last = groups[groups.length - 1];\n    if (last && last.name === o.group) last.options.push(o);\n    else groups.push({ name: o.group, options: [o] });\n  }\n\n  return (\n    <Menu.Root>\n      <Toolbar.Button\n        render={<Menu.Trigger />}\n        aria-label={active ? `${label}, ${active} active` : label}\n        className={cn(buttonClass, className)}\n      >\n        <Filter size={16} className=\"text-fg-2 transition-colors duration-150 group-hover/btn:text-fg group-data-popup-open/btn:text-fg\" />\n        <span className=\"@max-md:sr-only\">{label}</span>\n        <AnimatePresence initial={false}>\n          {active > 0 && (\n            // The badge grows its own width, so the search field beside it gives way smoothly instead of jumping.\n            <motion.span\n              key=\"badge\"\n              aria-hidden\n              initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.5, width: 0, marginLeft: -6 }}\n              animate={{ opacity: 1, scale: 1, width: \"auto\", marginLeft: 0 }}\n              exit={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.5, width: 0, marginLeft: -6 }}\n              transition={reduce ? { duration: 0.12 } : spring.snappy}\n              className=\"flex h-4 items-center overflow-hidden @max-md:absolute @max-md:-right-1.5 @max-md:-top-1.5\"\n            >\n              <span className=\"grid h-4 min-w-4 place-items-center rounded-full bg-fg px-1 text-[10.5px] font-medium leading-none text-frame tabular\">\n                <NumberFlow value={active} className=\"tabular\" />\n              </span>\n            </motion.span>\n          )}\n        </AnimatePresence>\n      </Toolbar.Button>\n      <Menu.Portal>\n        <Menu.Positioner side=\"bottom\" align=\"start\" sideOffset={6} collisionPadding={8} className=\"z-(--z-dropdown)\">\n          <Menu.Popup className={menuPopupClass}>\n            {groups.map((g, gi) => (\n              <Fragment key={g.name ?? gi}>\n                {gi > 0 && <Menu.Separator className=\"mx-2 my-1 h-px bg-line\" />}\n                <Menu.Group>\n                  {g.name && (\n                    <Menu.GroupLabel className=\"px-2 pb-1 pt-1.5 font-mono text-2xs uppercase tracking-[0.08em] text-fg-4\">{g.name}</Menu.GroupLabel>\n                  )}\n                  {g.options.map((o) => {\n                    const checked = value.includes(o.value);\n                    return (\n                      <Menu.CheckboxItem\n                        key={o.value}\n                        checked={checked}\n                        onCheckedChange={(next) => setValue(next ? [...value, o.value] : value.filter((v) => v !== o.value))}\n                        className={cn(menuItemClass, \"group/item\")}\n                      >\n                        <span\n                          className={cn(\n                            \"grid size-3.5 shrink-0 place-items-center rounded-[4px] border transition-[background-color,border-color,scale] duration-150\",\n                            \"group-active/item:scale-[0.86]\",\n                            checked ? \"border-fg bg-fg text-frame\" : \"border-fg-4 bg-raised group-data-highlighted/item:border-fg-3\",\n                          )}\n                        >\n                          <DrawnTick on={checked} />\n                        </span>\n                        <span className=\"min-w-0 flex-1 truncate\">{o.label}</span>\n                        {o.count != null && <span className=\"text-[12px] text-fg-4 tabular\">{o.count}</span>}\n                      </Menu.CheckboxItem>\n                    );\n                  })}\n                </Menu.Group>\n              </Fragment>\n            ))}\n            {active > 0 && (\n              <>\n                <Menu.Separator className=\"mx-2 my-1 h-px bg-line\" />\n                <Menu.Item onClick={() => setValue([])} closeOnClick={false} className={cn(menuItemClass, \"text-fg-2 data-highlighted:text-fg\")}>\n                  <X size={14} className=\"ml-px text-fg-3\" />\n                  Clear filters\n                </Menu.Item>\n              </>\n            )}\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction DrawnTick({ on }: { on: boolean }) {\n  const reduce = useReducedMotion();\n  return (\n    <svg viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden focusable=\"false\" className=\"size-3.5\">\n      <motion.path\n        d=\"M3.75 8.25 6.75 11.25 12.25 4.75\"\n        stroke=\"currentColor\"\n        strokeWidth={2}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        initial={false}\n        animate={{ pathLength: on ? 1 : 0, opacity: on ? 1 : 0 }}\n        transition={reduce ? { duration: 0 } : on ? { pathLength: { duration: 0.22, ease: ease.out }, opacity: { duration: 0.04 } } : { duration: 0.08 }}\n      />\n    </svg>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Segmented toggles: density and view\n * -----------------------------------------------------------------------------------------------*/\n\nexport type TableToolbarToggleOption = { value: string; label: string; icon: React.ReactNode };\n\nexport type TableToolbarToggleProps = {\n  options: TableToolbarToggleOption[];\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  /** Names the group for screen readers, e.g. \"Density\". */\n  \"aria-label\": string;\n  className?: string;\n};\n\n/** Icon-only segmented control. One pill slides between the options; each has a tooltip. */\nexport function TableToolbarToggle({ options, value: valueProp, defaultValue, onValueChange, className, ...rest }: TableToolbarToggleProps) {\n  const [value, setValue] = useControllableState({ value: valueProp, defaultValue: defaultValue ?? options[0]?.value ?? \"\", onChange: onValueChange });\n  const reduce = useReducedMotion();\n  const pillId = useId();\n\n  return (\n    <ToggleGroup\n      value={[value]}\n      // A segmented control always has one choice: pressing the current one keeps it.\n      onValueChange={(next) => next[0] && setValue(next[0])}\n      aria-label={rest[\"aria-label\"]}\n      className={cn(\"flex h-8 shrink-0 items-center rounded-lg border border-line-2 bg-frame p-0.5 shadow-[var(--shadow)]\", className)}\n    >\n      {options.map((o) => {\n        const on = o.value === value;\n        return (\n          <Tooltip.Root key={o.value}>\n            <Tooltip.Trigger\n              render={\n                <Toolbar.Button\n                  render={<Toggle value={o.value} />}\n                  aria-label={o.label}\n                  className={cn(\n                    \"relative grid h-full w-7 place-items-center rounded-md text-fg-3 transition-[color,scale] duration-150\",\n                    \"hover:text-fg-2 data-pressed:text-fg active:scale-[0.92] active:duration-75\",\n                    \"before:absolute before:-inset-x-0.5 before:-inset-y-2 before:content-[''] pointer-fine:before:hidden\",\n                    \"outline-none focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-fg-3\",\n                  )}\n                />\n              }\n            >\n              {on && (\n                <motion.span\n                  layoutId={pillId}\n                  aria-hidden\n                  transition={reduce ? { duration: 0 } : spring.snappy}\n                  className=\"absolute inset-0 rounded-md bg-raised shadow-[var(--shadow)] ring-1 ring-line-2\"\n                />\n              )}\n              <span className=\"relative\">{o.icon}</span>\n            </Tooltip.Trigger>\n            <Tooltip.Portal>\n              <Tooltip.Positioner side=\"top\" sideOffset={8} collisionPadding={8} className=\"z-(--z-tooltip)\">\n                <Tooltip.Popup\n                  className={cn(\n                    \"rounded-lg border border-line-2 bg-raised px-2 py-[3px] text-[12px] leading-4 text-fg shadow-pop outline-none\",\n                    \"origin-(--transform-origin) transition-[opacity,scale,translate] duration-150 ease-out-expo\",\n                    \"data-starting-style:scale-96 data-starting-style:translate-y-0.5 data-starting-style:opacity-0\",\n                    \"data-ending-style:opacity-0 data-ending-style:duration-100 data-instant:transition-none\",\n                  )}\n                >\n                  {o.label}\n                </Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        );\n      })}\n    </ToggleGroup>\n  );\n}\n\nexport type TableToolbarDensityProps = Omit<TableToolbarToggleProps, \"options\" | \"value\" | \"defaultValue\" | \"onValueChange\" | \"aria-label\"> & {\n  value?: TableDensity;\n  defaultValue?: TableDensity;\n  onValueChange?: (value: TableDensity) => void;\n  \"aria-label\"?: string;\n};\n\nexport function TableToolbarDensity({ onValueChange, defaultValue = \"comfortable\", ...rest }: TableToolbarDensityProps) {\n  return (\n    <TableToolbarToggle\n      aria-label=\"Row density\"\n      options={[\n        { value: \"compact\", label: \"Compact rows\", icon: <DensityGlyph gap={2.5} /> },\n        { value: \"comfortable\", label: \"Comfortable rows\", icon: <DensityGlyph gap={4} /> },\n      ]}\n      defaultValue={defaultValue}\n      onValueChange={(v) => onValueChange?.(v as TableDensity)}\n      {...rest}\n    />\n  );\n}\n\n// Lines spaced like the rows they stand for: four tight or three loose.\nfunction DensityGlyph({ gap }: { gap: number }) {\n  const count = gap < 3 ? 4 : 3;\n  const top = 8 - ((count - 1) * gap) / 2;\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.4} strokeLinecap=\"round\" aria-hidden focusable=\"false\">\n      {Array.from({ length: count }, (_, i) => (\n        <path key={i} d={`M3 ${top + i * gap}h10`} />\n      ))}\n    </svg>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Export\n * -----------------------------------------------------------------------------------------------*/\n\nexport type TableToolbarExportFormat = { value: string; label: string; hint?: string };\ntype ExportState = \"idle\" | \"busy\" | \"done\" | \"failed\";\n// Only ever called from event handlers and timers.\nconst now = () => performance.now();\n\nexport type TableToolbarExportProps = {\n  formats?: TableToolbarExportFormat[];\n  /** Runs the export. Return a promise to get the busy, done and failed states. */\n  onExport: (format: string) => void | Promise<unknown>;\n  label?: string;\n  className?: string;\n};\n\nconst defaultFormats: TableToolbarExportFormat[] = [\n  { value: \"csv\", label: \"CSV\", hint: \".csv\" },\n  { value: \"json\", label: \"JSON\", hint: \".json\" },\n];\n\nexport function TableToolbarExport({ formats = defaultFormats, onExport, label = \"Export\", className }: TableToolbarExportProps) {\n  const [state, setState] = useState<ExportState>(\"idle\");\n  const reduce = useReducedMotion();\n  const timers = useRef<number[]>([]);\n  useEffect(() => () => timers.current.forEach((t) => window.clearTimeout(t)), []);\n\n  const run = async (format: string) => {\n    if (state === \"busy\") return;\n    timers.current.forEach((t) => window.clearTimeout(t));\n    timers.current = [];\n    // No spinner for exports that finish inside 150ms; once shown, it stays at least 300ms.\n    let shownAt = 0;\n    timers.current.push(window.setTimeout(() => ((shownAt = now()), setState(\"busy\")), 150));\n    let outcome: ExportState = \"done\";\n    try {\n      await onExport(format);\n    } catch {\n      outcome = \"failed\";\n    }\n    window.clearTimeout(timers.current[0]);\n    const wait = shownAt ? Math.max(0, 300 - (now() - shownAt)) : 0;\n    timers.current.push(\n      window.setTimeout(() => {\n        setState(outcome);\n        timers.current.push(window.setTimeout(() => setState(\"idle\"), outcome === \"failed\" ? 2600 : 1600));\n      }, wait),\n    );\n  };\n\n  const labels = { idle: label, busy: \"Exporting…\", done: \"Exported\", failed: \"Failed\" } as const;\n  const icon =\n    state === \"busy\" ? <Loader size={16} className=\"animate-spin-slow\" /> : state === \"done\" ? <Check size={16} /> : state === \"failed\" ? <X size={16} /> : <Download size={16} />;\n\n  return (\n    <Menu.Root>\n      <Toolbar.Button\n        render={<Menu.Trigger />}\n        aria-label={labels[state]}\n        aria-busy={state === \"busy\" || undefined}\n        data-state={state}\n        className={cn(buttonClass, \"@max-md:ml-auto\", state === \"failed\" && \"text-danger\", state === \"busy\" && \"pointer-events-none\", className)}\n      >\n        <span className={cn(\"relative grid size-4 place-items-center\", state === \"idle\" ? \"text-fg-2 group-hover/btn:text-fg\" : state === \"done\" ? \"text-success\" : \"\")}>\n          <AnimatePresence initial={false}>\n            <motion.span\n              key={state}\n              className=\"absolute inset-0 grid place-items-center\"\n              initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.5, filter: \"blur(2px)\" }}\n              animate={{ opacity: 1, scale: 1, filter: \"blur(0px)\" }}\n              exit={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.5, filter: \"blur(2px)\" }}\n              transition={reduce ? { duration: 0.12 } : spring.pop}\n            >\n              {icon}\n            </motion.span>\n          </AnimatePresence>\n        </span>\n        {/* Every label shares one grid cell, so the button keeps the width of the longest. */}\n        <span className=\"grid overflow-hidden py-1 text-left @max-md:sr-only\" aria-hidden>\n          {Object.values(labels).map((l) => (\n            <span key={l} className=\"invisible col-start-1 row-start-1\">{l}</span>\n          ))}\n          <AnimatePresence initial={false}>\n            <motion.span\n              key={state}\n              className=\"col-start-1 row-start-1\"\n              initial={reduce ? { opacity: 0 } : { opacity: 0, y: 6, filter: \"blur(2px)\" }}\n              animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n              exit={reduce ? { opacity: 0 } : { opacity: 0, y: -6, filter: \"blur(2px)\" }}\n              transition={{ duration: reduce ? 0.12 : 0.2, ease: ease.out }}\n            >\n              {labels[state]}\n            </motion.span>\n          </AnimatePresence>\n        </span>\n      </Toolbar.Button>\n      <Menu.Portal>\n        <Menu.Positioner side=\"bottom\" align=\"end\" sideOffset={6} collisionPadding={8} className=\"z-(--z-dropdown)\">\n          <Menu.Popup className={cn(menuPopupClass, \"min-w-44\")}>\n            <Menu.Group>\n              <Menu.GroupLabel className=\"px-2 pb-1 pt-1.5 font-mono text-2xs uppercase tracking-[0.08em] text-fg-4\">Export as</Menu.GroupLabel>\n              {formats.map((f) => (\n                <Menu.Item key={f.value} onClick={() => run(f.value)} className={menuItemClass}>\n                  <span className=\"flex-1\">{f.label}</span>\n                  {f.hint && <span className=\"font-mono text-[11px] text-fg-4\">{f.hint}</span>}\n                </Menu.Item>\n              ))}\n            </Menu.Group>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n      <span role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n        {state === \"done\" ? \"Export ready\" : state === \"failed\" ? \"Export failed. Try again.\" : \"\"}\n      </span>\n    </Menu.Root>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://www.stealth.pm/ui/table-toolbar"
}
