{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "message-queue",
  "title": "Message Queue",
  "description": "Pending user turns that remain visible, removable, and immediately sendable while an agent is busy.",
  "dependencies": [
    "@base-ui/react@^1.8.0",
    "@solar-icons/react@^2.2.0",
    "cn@^0.3.0",
    "motion@^13.4.0"
  ],
  "registryDependencies": [
    "button",
    "https://pastaui.com/r/pastaui-styles.json"
  ],
  "files": [
    {
      "path": "src/components/ai/message-queue.tsx",
      "content": "import { Menu } from '@base-ui/react/menu'\nimport { AnimatePresence, Reorder, useDragControls, useReducedMotion } from 'motion/react'\nimport { Forward2Icon } from '@solar-icons/react/bold-duotone/forward-2'\nimport { ListArrowDownMinimalisticIcon as QueueIcon } from '@solar-icons/react/linear/list-arrow-down-minimalistic'\nimport { TrashBinMinimalisticIcon as TrashIcon } from '@solar-icons/react/linear/trash-bin-minimalistic'\nimport { MenuDotsIcon as MoreIcon } from '@solar-icons/react/linear/menu-dots'\nimport * as React from 'react'\n\nimport { Button } from '@/components/ui/button'\nimport { cn } from 'cn'\n\ninterface QueuedMessage {\n  attachments?: number\n  content: string\n  id: string\n  status?: 'queued' | 'sending'\n}\n\ninterface MessageQueueProps extends React.ComponentProps<'section'> {\n  items: QueuedMessage[]\n  onRemove?: (id: string) => void\n  onSendNow?: (id: string) => void\n  onMoveToTop?: (id: string) => void\n  onReorder?: (items: QueuedMessage[]) => void\n}\n\nfunction QueueRow({\n  item,\n  enabled,\n  onMove,\n  children,\n}: {\n  item: QueuedMessage\n  enabled: boolean\n  onMove: (direction: number) => void\n  children: React.ReactNode\n}) {\n  const controls = useDragControls()\n  const reducedMotion = useReducedMotion()\n  return (\n    <Reorder.Item\n      value={item}\n      dragListener={false}\n      dragControls={controls}\n      className=\"group/queue-row relative flex items-center gap-3 rounded-md bg-card py-1.5\"\n      whileDrag={{ zIndex: 10 }}\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      exit={{ opacity: 0 }}\n      transition={{\n        default: reducedMotion ? { duration: 0 } : { type: 'spring', stiffness: 500, damping: 40 },\n        opacity: { duration: 0.15, ease: [0.23, 1, 0.32, 1] },\n      }}\n    >\n      <button\n        type=\"button\"\n        disabled={!enabled}\n        aria-label={`Reorder message: ${item.content}`}\n        title=\"Drag to reorder, or use Up and Down arrow keys\"\n        className=\"relative flex h-7 w-4 shrink-0 touch-none items-center justify-center rounded-sm text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring active:cursor-grabbing enabled:cursor-grab\"\n        onPointerDown={(event) => {\n          if (enabled) controls.start(event)\n        }}\n        onKeyDown={(event) => {\n          if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n            event.preventDefault()\n            onMove(event.key === 'ArrowUp' ? -1 : 1)\n          }\n        }}\n      >\n        <QueueIcon\n          aria-hidden=\"true\"\n          className={cn(\n            'absolute size-4 opacity-100 transition-opacity duration-150 ease-out',\n            enabled && 'group-focus-within/queue-row:opacity-0 group-hover/queue-row:opacity-0',\n          )}\n        />\n        <svg\n          className={cn(\n            'absolute size-4 opacity-0 transition-opacity duration-150 ease-out',\n            enabled && 'group-focus-within/queue-row:opacity-100 group-hover/queue-row:opacity-100',\n          )}\n          aria-hidden=\"true\"\n          width=\"16\"\n          height=\"16\"\n          viewBox=\"0 0 16 16\"\n          fill=\"currentColor\"\n        >\n          {[4, 8, 12].map((y) => (\n            <React.Fragment key={y}>\n              <circle cx=\"5\" cy={y} r=\"1\" />\n              <circle cx=\"11\" cy={y} r=\"1\" />\n            </React.Fragment>\n          ))}\n        </svg>\n      </button>\n      {children}\n    </Reorder.Item>\n  )\n}\n\nfunction MessageQueue({\n  className,\n  items,\n  onRemove,\n  onSendNow,\n  onMoveToTop,\n  onReorder,\n  ...props\n}: MessageQueueProps) {\n  return (\n    <section\n      aria-label=\"Queued messages\"\n      data-slot=\"message-queue\"\n      className={cn(\n        'rounded-[16px] bg-card p-3 font-sans text-sm font-[450] tracking-[-0.05px] text-muted-foreground shadow-card',\n        className,\n      )}\n      {...props}\n    >\n      <Reorder.Group as=\"ol\" axis=\"y\" values={items} onReorder={(next) => onReorder?.(next)}>\n        <AnimatePresence initial={false}>\n          {items.map((item, index) => (\n            <QueueRow\n              key={item.id}\n              item={item}\n              enabled={!!onReorder}\n              onMove={(direction) => {\n                const target = index + direction\n                if (target < 0 || target >= items.length) return\n                const next = [...items]\n                ;[next[index], next[target]] = [next[target], next[index]]\n                onReorder?.(next)\n              }}\n            >\n              <p className=\"line-clamp-2 min-w-0 flex-1 text-sm leading-5 font-[450] text-muted-foreground\">\n                {item.content}\n              </p>\n              {item.attachments ? (\n                <span className=\"shrink-0 text-xs leading-5 font-[450] text-muted-foreground\">\n                  {item.attachments} files\n                </span>\n              ) : null}\n              <div className=\"flex shrink-0 items-center gap-1\">\n                <Button\n                  type=\"button\"\n                  disabled={item.status === 'sending'}\n                  onClick={() => onSendNow?.(item.id)}\n                  size=\"sm\"\n                  variant=\"ghost\"\n                  className=\"gap-1.5 text-sm font-[450] tracking-[-0.05px] text-muted-foreground\"\n                >\n                  <Forward2Icon className=\"size-4\" />\n                  {item.status === 'sending' ? 'Steering…' : 'Steer'}\n                </Button>\n                <Button\n                  type=\"button\"\n                  aria-label={`Remove queued message: ${item.content}`}\n                  onClick={() => onRemove?.(item.id)}\n                  size=\"icon-sm\"\n                  variant=\"ghost\"\n                >\n                  <TrashIcon className=\"size-4\" />\n                </Button>\n                <Menu.Root>\n                  <Menu.Trigger\n                    render={\n                      <Button\n                        type=\"button\"\n                        aria-label={`More actions for: ${item.content}`}\n                        size=\"icon-sm\"\n                        variant=\"ghost\"\n                      />\n                    }\n                  >\n                    <MoreIcon className=\"size-4\" />\n                  </Menu.Trigger>\n                  <Menu.Portal>\n                    <Menu.Positioner side=\"bottom\" align=\"end\" sideOffset={6} className=\"z-50\">\n                      <Menu.Popup className=\"min-w-36 origin-(--transform-origin) rounded-[16px] bg-card p-1 font-sans text-sm font-[450] tracking-[-0.05px] text-muted-foreground shadow-card transition-[opacity,scale] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] outline-none data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0 motion-reduce:transition-opacity\">\n                        <Menu.Item\n                          className=\"cursor-pointer rounded-[12px] px-3 py-2 outline-none data-highlighted:bg-muted\"\n                          onClick={() => navigator.clipboard.writeText(item.content)}\n                        >\n                          Copy message\n                        </Menu.Item>\n                        {onMoveToTop && (\n                          <Menu.Item\n                            disabled={index === 0}\n                            className=\"cursor-pointer rounded-[12px] px-3 py-2 outline-none data-highlighted:bg-muted data-disabled:opacity-40\"\n                            onClick={() => onMoveToTop(item.id)}\n                          >\n                            Move to top\n                          </Menu.Item>\n                        )}\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.Root>\n              </div>\n            </QueueRow>\n          ))}\n        </AnimatePresence>\n      </Reorder.Group>\n      {!items.length && (\n        <p className=\"px-2 py-3 text-xs leading-5 font-[450] text-muted-foreground\">\n          No queued messages.\n        </p>\n      )}\n    </section>\n  )\n}\n\nexport { MessageQueue }\nexport type { MessageQueueProps, QueuedMessage }\n",
      "type": "registry:component",
      "target": "@components/ai/message-queue.tsx"
    }
  ],
  "type": "registry:component"
}