{
  "name": "prompt-input",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "prompt-input.tsx",
      "content": "\"use client\";\r\n\r\nimport { css, themeVars as theme } from \"@yugnex/core\";\r\nimport { useControllableState } from \"@yugnex/core/client\";\r\nimport {\r\n  forwardRef,\r\n  useCallback,\r\n  useEffect,\r\n  useRef,\r\n  type KeyboardEvent,\r\n  type ReactNode,\r\n  type TextareaHTMLAttributes,\r\n} from \"react\";\r\n\r\nconst shellClass = css({\r\n  display: \"flex\",\r\n  flexDirection: \"column\",\r\n  gap: theme.space[2],\r\n  padding: theme.space[3],\r\n  borderRadius: theme.radius.lg,\r\n  border: `1px solid ${theme.color.input}`,\r\n  backgroundColor: theme.color.card,\r\n  transitionProperty: \"border-color, box-shadow\",\r\n  transitionDuration: theme.duration.fast,\r\n  transitionTimingFunction: theme.easing.standard,\r\n  \"&:focus-within\": {\r\n    borderColor: theme.color.ring,\r\n    boxShadow: `0 0 0 3px ${theme.color.accent}`,\r\n  },\r\n  '&[data-disabled=\"true\"]': {\r\n    opacity: 0.6,\r\n    cursor: \"not-allowed\",\r\n  },\r\n});\r\n\r\nconst textareaClass = css({\r\n  width: \"100%\",\r\n  resize: \"none\",\r\n  border: \"none\",\r\n  outline: \"none\",\r\n  background: \"transparent\",\r\n  color: theme.color.foreground,\r\n  fontFamily: theme.fontFamily.sans,\r\n  fontSize: theme.fontSize.sm,\r\n  lineHeight: theme.lineHeight.base,\r\n  padding: 0,\r\n  maxHeight: \"16rem\",\r\n  overflowY: \"auto\",\r\n  \"&::placeholder\": { color: theme.color.mutedForeground },\r\n  \"&:disabled\": { cursor: \"not-allowed\" },\r\n});\r\n\r\nconst footerClass = css({\r\n  display: \"flex\",\r\n  alignItems: \"center\",\r\n  justifyContent: \"space-between\",\r\n  gap: theme.space[2],\r\n});\r\n\r\nconst hintClass = css({\r\n  fontSize: theme.fontSize.xs,\r\n  color: theme.color.mutedForeground,\r\n});\r\n\r\nconst counterOverClass = css({ color: theme.color.destructive });\r\n\r\nexport interface PromptInputProps\r\n  extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, \"value\" | \"onChange\" | \"onSubmit\"> {\r\n  value?: string;\r\n  defaultValue?: string;\r\n  onValueChange?: (value: string) => void;\r\n  /** Fired on Enter (without Shift) and by any submit affordance you render in `actions`. */\r\n  onSubmit?: (value: string) => void;\r\n  /** Rendered on the right of the footer — typically a send Button. */\r\n  actions?: ReactNode;\r\n  /** Rendered on the left of the footer — attachments, model picker, etc. */\r\n  leading?: ReactNode;\r\n  /** Show a \"characters used\" counter and mark it destructive past this length. */\r\n  maxLength?: number;\r\n  /** Rows to start at before auto-growing. */\r\n  minRows?: number;\r\n  isLoading?: boolean;\r\n}\r\n\r\n/**\r\n * The prompt box for a chat or agent UI: auto-grows with content, submits on\r\n * Enter (Shift+Enter inserts a newline), and exposes footer slots for a send\r\n * button, attachments, or a model picker.\r\n */\r\nexport const PromptInput = forwardRef<HTMLTextAreaElement, PromptInputProps>(function PromptInput(\r\n  {\r\n    value,\r\n    defaultValue = \"\",\r\n    onValueChange,\r\n    onSubmit,\r\n    actions,\r\n    leading,\r\n    maxLength,\r\n    minRows = 1,\r\n    isLoading,\r\n    disabled,\r\n    placeholder = \"Send a message…\",\r\n    onKeyDown,\r\n    className,\r\n    ...props\r\n  },\r\n  ref,\r\n) {\r\n  const [text, setText] = useControllableState({\r\n    value,\r\n    defaultValue,\r\n    onChange: onValueChange,\r\n  });\r\n\r\n  const innerRef = useRef<HTMLTextAreaElement | null>(null);\r\n  const isDisabled = disabled || isLoading;\r\n\r\n  const resize = useCallback(() => {\r\n    const el = innerRef.current;\r\n    if (!el) return;\r\n    // Reset first so the scrollHeight reflects a shrink, not just growth.\r\n    el.style.height = \"auto\";\r\n    el.style.height = `${el.scrollHeight}px`;\r\n  }, []);\r\n\r\n  useEffect(() => {\r\n    resize();\r\n  }, [text, resize]);\r\n\r\n  function handleKeyDown(event: KeyboardEvent<HTMLTextAreaElement>) {\r\n    onKeyDown?.(event);\r\n    if (event.defaultPrevented) return;\r\n    if (event.key === \"Enter\" && !event.shiftKey && !event.nativeEvent.isComposing) {\r\n      event.preventDefault();\r\n      if (!isDisabled && text.trim()) onSubmit?.(text);\r\n    }\r\n  }\r\n\r\n  const over = maxLength != null && text.length > maxLength;\r\n\r\n  return (\r\n    <div className={className ? `${shellClass} ${className}` : shellClass} data-disabled={isDisabled || undefined}>\r\n      <textarea\r\n        ref={(node) => {\r\n          innerRef.current = node;\r\n          if (typeof ref === \"function\") ref(node);\r\n          else if (ref) ref.current = node;\r\n        }}\r\n        rows={minRows}\r\n        className={textareaClass}\r\n        value={text}\r\n        placeholder={placeholder}\r\n        disabled={isDisabled}\r\n        aria-busy={isLoading || undefined}\r\n        onChange={(event) => setText(event.target.value)}\r\n        onKeyDown={handleKeyDown}\r\n        {...props}\r\n      />\r\n      {actions || leading || maxLength != null ? (\r\n        <div className={footerClass}>\r\n          <div className={hintClass}>{leading}</div>\r\n          <div className={footerClass}>\r\n            {maxLength != null ? (\r\n              <span className={over ? `${hintClass} ${counterOverClass}` : hintClass}>\r\n                {text.length}/{maxLength}\r\n              </span>\r\n            ) : null}\r\n            {actions}\r\n          </div>\r\n        </div>\r\n      ) : null}\r\n    </div>\r\n  );\r\n});\r\n",
      "type": "registry:component"
    }
  ]
}