{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "compact-prompt-area",
  "title": "Compact Prompt Area",
  "description": "A compact pill-shaped prompt input that expands downward on focus. Wraps PromptArea with circular plus and submit buttons and a customizable slot.",
  "registryDependencies": [
    "prompt-area"
  ],
  "files": [
    {
      "path": "packages/prompt-area/src/compact-prompt-area/compact-prompt-area.tsx",
      "content": "'use client'\n\nimport { useCallback, useImperativeHandle, useRef, useState } from 'react'\nimport { cn } from '@/lib/utils'\nimport { PromptArea } from '@/registry/new-york/blocks/prompt-area/prompt-area'\nimport { BLUR_DELAY_MS } from '@/registry/new-york/blocks/prompt-area/use-prompt-area-events'\nimport type { PromptAreaHandle } from '@/registry/new-york/blocks/prompt-area/types'\nimport type { CompactPromptAreaProps } from './types'\n\ntype IconProps = { className?: string }\n\n/** Shared SVG wrapper matching the lucide icon defaults (no dependency). */\nfunction Svg({ className, children }: IconProps & { children: React.ReactNode }) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden=\"true\"\n      className={className}>\n      {children}\n    </svg>\n  )\n}\n\nconst Plus = ({ className }: IconProps) => (\n  <Svg className={className}>\n    <path d=\"M5 12h14\" />\n    <path d=\"M12 5v14\" />\n  </Svg>\n)\nconst ArrowUp = ({ className }: IconProps) => (\n  <Svg className={className}>\n    <path d=\"m5 12 7-7 7 7\" />\n    <path d=\"M12 19V5\" />\n  </Svg>\n)\n\n/**\n * Shared timing for the collapse/expand morph. The same duration + easing is\n * used by the container (border-radius), the editor region (padding) and the\n * inner editor's `min-height`, so every part of the box eases in lock-step.\n * `prefers-reduced-motion` switches the change instantly.\n */\nconst MORPH_TIMING =\n  'duration-[240ms] ease-[cubic-bezier(0.33,1,0.68,1)] motion-reduce:transition-none'\n\n/**\n * CompactPromptArea – A pill-shaped prompt input that sits on a single row\n * and expands downward on focus.\n *\n * - Left: circular plus button\n * - Middle: PromptArea text input (expands down when focused)\n * - Right: optional slot + circular submit button\n *\n * Reuses PromptArea internally with autoGrow enabled.\n *\n * @example\n * ```tsx\n * <CompactPromptArea\n *   value={segments}\n *   onChange={setSegments}\n *   placeholder=\"Ask anything...\"\n *   onSubmit={handleSubmit}\n *   onPlusClick={() => setMenuOpen(true)}\n *   beforeSubmitSlot={<button aria-label=\"Voice\"><Mic className=\"size-4\" /></button>}\n * />\n * ```\n */\nexport function CompactPromptArea({\n  value,\n  onChange,\n  triggers,\n  placeholder,\n  disabled = false,\n  markdown,\n  onSubmit,\n  onEscape,\n  onChipClick,\n  onChipAdd,\n  onChipDelete,\n  onPaste,\n  images,\n  onImagePaste,\n  onImageRemove,\n  files,\n  onFileRemove,\n  plusButtonIcon,\n  onPlusClick,\n  submitButtonIcon,\n  beforeSubmitSlot,\n  maxHeight = 320,\n  className,\n  'aria-label': ariaLabel,\n  'data-test-id': dataTestId,\n  ref,\n}: CompactPromptAreaProps & { ref?: React.Ref<PromptAreaHandle> }) {\n  const promptRef = useRef<PromptAreaHandle>(null)\n  const containerRef = useRef<HTMLDivElement>(null)\n  const [isFocused, setIsFocused] = useState(false)\n\n  // Forward a stable, null-safe handle that proxies to the inner PromptArea.\n  // Reading `promptRef.current` lazily on each call avoids both a non-null\n  // assertion and capturing a stale snapshot at mount time.\n  useImperativeHandle(\n    ref,\n    () => ({\n      focus: () => promptRef.current?.focus(),\n      blur: () => promptRef.current?.blur(),\n      insertChip: (chip) => promptRef.current?.insertChip(chip),\n      getPlainText: () => promptRef.current?.getPlainText() ?? '',\n      clear: () => promptRef.current?.clear(),\n      setText: (text) => promptRef.current?.setText(text),\n      appendText: (text) => promptRef.current?.appendText(text),\n      getCursorPosition: () => promptRef.current?.getCursorPosition() ?? null,\n      setCursorPosition: (offset) => promptRef.current?.setCursorPosition(offset),\n      setCursorToEnd: () => promptRef.current?.setCursorToEnd(),\n      getSelection: () => promptRef.current?.getSelection() ?? null,\n      setSelection: (start, end) => promptRef.current?.setSelection(start, end),\n    }),\n    [],\n  )\n\n  const isEmpty =\n    value.length === 0 || (value.length === 1 && value[0].type === 'text' && value[0].text === '')\n\n  const isExpanded = isFocused || !isEmpty\n\n  const handleContainerFocus = useCallback(() => {\n    setIsFocused(true)\n  }, [])\n\n  const handleContainerBlur = useCallback(() => {\n    setTimeout(() => {\n      if (!containerRef.current?.contains(document.activeElement)) {\n        setIsFocused(false)\n      }\n    }, BLUR_DELAY_MS)\n  }, [])\n\n  const handleSubmit = useCallback(() => {\n    onSubmit?.(value)\n  }, [onSubmit, value])\n\n  return (\n    <div\n      ref={containerRef}\n      onFocus={handleContainerFocus}\n      onBlur={handleContainerBlur}\n      aria-label={ariaLabel}\n      data-test-id={dataTestId}\n      data-expanded={isExpanded || undefined}\n      className={cn(\n        'compact-prompt-area relative isolate overflow-hidden',\n        'bg-background border',\n        // Only border-radius / colour / shadow tween here — the box's height\n        // is driven by the editor region below, so it eases along with it.\n        'transition-[border-radius,box-shadow,border-color]',\n        MORPH_TIMING,\n        isExpanded ? 'rounded-2xl shadow-sm' : 'rounded-3xl',\n        className,\n      )}>\n      {/* Editor region – the only in-flow child, so its height + padding drive\n          the container's height. Padding eases between a single inset row\n          (collapsed) and a roomy box that reserves space for the pinned\n          toolbar (expanded), giving a smooth, tween-able morph. */}\n      <div\n        onClick={() => promptRef.current?.focus()}\n        className={cn(\n          'min-w-0 cursor-text',\n          'transition-[padding]',\n          MORPH_TIMING,\n          // Collapsed: leave room on one row for the pinned plus (left) and the\n          // controls (right) — wider when a slot sits before the submit button.\n          // Expanded: roomy box with bottom space reserved for the toolbar.\n          isExpanded\n            ? 'pt-4 pr-5 pb-14 pl-5'\n            : cn('py-3 pl-[3.25rem]', beforeSubmitSlot ? 'pr-[5.5rem]' : 'pr-[3.25rem]'),\n        )}>\n        <PromptArea\n          ref={promptRef}\n          value={value}\n          onChange={onChange}\n          triggers={triggers}\n          placeholder={placeholder}\n          disabled={disabled}\n          markdown={markdown}\n          onSubmit={handleSubmit}\n          onEscape={onEscape}\n          onChipClick={onChipClick}\n          onChipAdd={onChipAdd}\n          onChipDelete={onChipDelete}\n          onPaste={onPaste}\n          images={images}\n          onImagePaste={onImagePaste}\n          onImageRemove={onImageRemove}\n          files={files}\n          onFileRemove={onFileRemove}\n          autoGrow\n          minHeight={isExpanded ? 48 : 24}\n          maxHeight={maxHeight}\n        />\n      </div>\n\n      {/* Plus button – pinned bottom-left in BOTH states (no teleport). As the\n          box grows it simply glides down with the bottom edge. */}\n      <button\n        type=\"button\"\n        onClick={onPlusClick}\n        disabled={disabled}\n        className={cn(\n          'absolute bottom-1.5 left-1.5 z-10 flex size-9 shrink-0 items-center justify-center',\n          'rounded-xl transition-colors',\n          'bg-muted text-muted-foreground',\n          'hover:bg-accent hover:text-foreground',\n          'disabled:pointer-events-none disabled:opacity-50',\n        )}\n        aria-label=\"Add attachment\">\n        {plusButtonIcon ?? <Plus className=\"size-4\" />}\n      </button>\n\n      {/* Right controls – pinned bottom-right in BOTH states (slot + submit). */}\n      <div className=\"absolute right-1.5 bottom-1.5 z-10 flex items-center gap-1.5\">\n        {beforeSubmitSlot}\n        <button\n          type=\"button\"\n          onClick={handleSubmit}\n          disabled={disabled || isEmpty}\n          className={cn(\n            'flex size-9 shrink-0 items-center justify-center rounded-xl transition-colors',\n            'bg-primary text-primary-foreground',\n            'hover:bg-primary/90',\n            'disabled:pointer-events-none disabled:opacity-50',\n          )}\n          aria-label=\"Send message\">\n          {submitButtonIcon ?? <ArrowUp className=\"size-4\" />}\n        </button>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component"
    },
    {
      "path": "packages/prompt-area/src/compact-prompt-area/types.ts",
      "content": "import type {\n  Segment,\n  TriggerConfig,\n  ChipSegment,\n  PromptAreaImage,\n  PromptAreaFile,\n} from '../prompt-area/types'\n\nexport type CompactPromptAreaProps = {\n  /** The document segments (controlled) */\n  value: Segment[]\n  /** Called when the content changes */\n  onChange: (segments: Segment[]) => void\n  /** Trigger configurations */\n  triggers?: TriggerConfig[]\n  /** Placeholder text when empty. Pass an array to animate between them. */\n  placeholder?: string | string[]\n  /** Whether the input is disabled */\n  disabled?: boolean\n  /** Whether to render simple inline markdown */\n  markdown?: boolean\n  /** Called when Enter is pressed (without Shift) */\n  onSubmit?: (segments: Segment[]) => void\n  /** Called when Escape is pressed */\n  onEscape?: () => void\n  /** Called when a chip element is clicked */\n  onChipClick?: (chip: ChipSegment) => void\n  /** Called when a new chip is added */\n  onChipAdd?: (chip: ChipSegment) => void\n  /** Called when a chip is deleted */\n  onChipDelete?: (chip: ChipSegment) => void\n  /** Called after content is pasted */\n  onPaste?: (data: { segments: Segment[]; source: 'internal' | 'external' }) => void\n  /** Array of image attachments */\n  images?: PromptAreaImage[]\n  /** Called when the user pastes an image */\n  onImagePaste?: (file: File) => void\n  /** Called when the user removes an image */\n  onImageRemove?: (image: PromptAreaImage) => void\n  /** Array of file attachments */\n  files?: PromptAreaFile[]\n  /** Called when the user removes a file */\n  onFileRemove?: (file: PromptAreaFile) => void\n\n  // ---- Compact-specific props ----\n\n  /** Icon for the circular plus button on the left. Defaults to Plus icon. */\n  plusButtonIcon?: React.ReactNode\n  /** Click handler for the plus button */\n  onPlusClick?: () => void\n  /** Icon for the circular submit button on the right. Defaults to ArrowUp icon. */\n  submitButtonIcon?: React.ReactNode\n  /** Slot rendered immediately before the submit button (e.g., mic button) */\n  beforeSubmitSlot?: React.ReactNode\n  /** Maximum height the expanded area can reach in pixels. Defaults to 320. */\n  maxHeight?: number\n  /** Additional CSS class for the outer container */\n  className?: string\n  /** Accessible label */\n  'aria-label'?: string\n  /** data-test-id for e2e testing */\n  'data-test-id'?: string\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}