@fanvue/ui 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -82,6 +82,7 @@ const ChatInput = React__namespace.forwardRef(
82
82
  toolbarRight,
83
83
  selectOptions,
84
84
  selectValue,
85
+ selectDisabled = false,
85
86
  onSelectChange,
86
87
  attachments,
87
88
  onAttachmentRemove,
@@ -159,7 +160,7 @@ const ChatInput = React__namespace.forwardRef(
159
160
  options: selectOptions,
160
161
  value: selectValue,
161
162
  onChange: onSelectChange,
162
- disabled,
163
+ disabled: disabled || selectDisabled,
163
164
  selectedOption
164
165
  }
165
166
  ) : null);
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInput.cjs","sources":["../../../../src/components/ChatInput/ChatInput.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { AddIcon } from \"../Icons/AddIcon\";\nimport { ArrowUpIcon } from \"../Icons/ArrowUpIcon\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** A single image thumbnail in the built-in attachment strip. */\nexport interface ChatInputAttachmentItem {\n /** Stable id passed to {@link ChatInputProps.onAttachmentRemove} and used as React `key`. */\n id: string;\n /** Image URL for the thumbnail. */\n src: string;\n /** Optional value passed to the remove control `aria-label`. */\n ariaLabel?: string;\n}\n\n/** A single option for the inline model/dropdown selector. */\nexport interface ChatInputSelectOption {\n /** Unique value for this option. */\n value: string;\n /** Display label. */\n label: string;\n /** Optional icon rendered to the left of the label. */\n icon?: React.ReactNode;\n}\n\n/**\n * Props for {@link ChatInput}. Standard textarea HTML attributes are forwarded to the inner\n * `<textarea>` except `className` (applied to the outer container), `rows` (use `minRows`), and\n * `onSubmit` (replaced by the chat message submit callback).\n */\nexport interface ChatInputProps\n extends Omit<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n \"className\" | \"rows\" | \"onSubmit\"\n > {\n /** Minimum number of visible rows. @default 1 */\n minRows?: number;\n /** Maximum number of visible rows before scrolling. @default 6 */\n maxRows?: number;\n /** Whether a submission is in progress (disables submit, shows visual feedback). @default false */\n loading?: boolean;\n /**\n * Callback fired when the user submits (clicks the send button or presses Enter without Shift).\n * Receives the current trimmed text value.\n */\n onSubmit?: (value: string) => void;\n /**\n * When `true`, renders an \"attach file\" button in the bottom-left toolbar.\n * @default false\n */\n showFileButton?: boolean;\n /** Callback fired when the attach-file button is clicked. Only relevant when `showFileButton` is `true`. */\n onFileClick?: () => void;\n /** Accessible label for the attach-file button. @default \"Attach file\" */\n fileButtonAriaLabel?: string;\n /** Accessible label for the submit button. @default \"Send message\" */\n submitAriaLabel?: string;\n /** Icon element for the submit button. @default `<ArrowUpIcon />` */\n submitIcon?: React.ReactNode;\n /**\n * Optional content rendered in the bottom-right toolbar, to the left of the submit button.\n * When provided, takes precedence over the built-in `selectOptions` dropdown.\n */\n toolbarRight?: React.ReactNode;\n /**\n * Options for the built-in inline dropdown selector (e.g. model picker).\n * Ignored when `toolbarRight` is provided.\n */\n selectOptions?: ChatInputSelectOption[];\n /** Currently selected value for the built-in dropdown. Should match one of `selectOptions[].value`. */\n selectValue?: string;\n /** Callback fired when the user picks a different dropdown option. */\n onSelectChange?: (value: string) => void;\n /**\n * Image attachments shown in the built-in thumbnail strip. Ignored when {@link ChatInputProps.attachmentPreviews}\n * is provided (including `null`).\n */\n attachments?: ChatInputAttachmentItem[];\n /**\n * Called when the user removes a built-in thumbnail. The remove button is disabled when this is\n * omitted or the input is {@link ChatInputProps.disabled}.\n */\n onAttachmentRemove?: (id: string) => void;\n /**\n * Replaces the built-in attachment strip entirely. When set to any value other than `undefined`\n * (including `null` or `[]`), {@link ChatInputProps.attachments} is ignored.\n */\n attachmentPreviews?: React.ReactNode;\n /** Additional className applied to the outermost container. */\n className?: string;\n}\n\nconst LINE_HEIGHT = 18;\nconst TEXTAREA_PY = 12;\n\nfunction calculateHeight(rows: number): number {\n return LINE_HEIGHT * rows + TEXTAREA_PY * 2;\n}\n\ninterface ChatInputDefaultAttachmentThumbnailsProps {\n attachments: ChatInputAttachmentItem[];\n onAttachmentRemove?: (id: string) => void;\n disabled?: boolean;\n}\n\nfunction ChatInputDefaultAttachmentThumbnails({\n attachments,\n onAttachmentRemove,\n disabled = false,\n}: ChatInputDefaultAttachmentThumbnailsProps) {\n return attachments.map((item) => (\n <div\n key={item.id}\n className=\"relative size-16 shrink-0 overflow-hidden rounded-sm border border-neutral-200 bg-background-secondary\"\n >\n <img src={item.src} alt=\"\" className=\"size-full object-cover\" />\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={item.ariaLabel ? `Remove ${item.ariaLabel}` : \"Remove attachment\"}\n icon={<CloseIcon className=\"!size-3\" />}\n disabled={disabled || !onAttachmentRemove}\n onClick={() => onAttachmentRemove?.(item.id)}\n className=\"absolute top-0.5 right-0.5 size-5 bg-neutral-900/40 p-1 text-white hover:bg-neutral-900/55\"\n />\n </div>\n ));\n}\n\n/**\n * A chat-style multi-line input with an integrated toolbar containing an\n * optional file-attach button, optional right-side controls (e.g. a model\n * selector), and a submit button — all inside a single bordered container.\n *\n * Designed to behave like modern AI chat inputs: auto-grows with content,\n * submits on Enter (Shift+Enter for newlines), and keeps controls inline.\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Type a message...\"\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Ask the agent...\"\n * showFileButton\n * onFileClick={() => openFilePicker()}\n * selectOptions={[\n * { value: \"fanvue-ai\", label: \"Fanvue AI\", icon: <AIIcon className=\"size-4\" /> },\n * { value: \"example\", label: \"Example\", icon: <BulbIcon className=\"size-4\" /> },\n * ]}\n * selectValue=\"fanvue-ai\"\n * onSelectChange={(v) => setModel(v)}\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachments={files}\n * onAttachmentRemove={(id) => setFiles((prev) => prev.filter((f) => f.id !== id))}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachmentPreviews={<CustomVideoStrip items={items} />}\n * />\n * ```\n */\nexport const ChatInput = React.forwardRef<HTMLTextAreaElement, ChatInputProps>(\n (\n {\n className,\n minRows = 1,\n maxRows = 6,\n disabled = false,\n loading = false,\n value,\n defaultValue,\n placeholder,\n maxLength,\n \"aria-label\": ariaLabel,\n onChange,\n onKeyDown,\n onSubmit,\n showFileButton = false,\n onFileClick,\n fileButtonAriaLabel = \"Attach file\",\n submitAriaLabel = \"Send message\",\n submitIcon,\n toolbarRight,\n selectOptions,\n selectValue,\n onSelectChange,\n attachments,\n onAttachmentRemove,\n attachmentPreviews,\n style,\n ...textareaProps\n },\n ref,\n ) => {\n const internalRef = React.useRef<HTMLTextAreaElement>(null);\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const resolvedValue = value !== undefined ? value : internalValue;\n const isControlled = value !== undefined;\n\n const mergedRef = React.useCallback(\n (node: HTMLTextAreaElement | null) => {\n (internalRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n }\n },\n [ref],\n );\n\n const adjustHeight = React.useCallback(() => {\n const textarea = internalRef.current;\n if (!textarea) return;\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n textarea.style.height = `${minHeight}px`;\n const desired = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);\n textarea.style.height = `${desired}px`;\n }, [minRows, maxRows]);\n\n React.useEffect(() => {\n adjustHeight();\n }, [resolvedValue, adjustHeight]);\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (!isControlled) {\n setInternalValue(e.target.value);\n }\n onChange?.(e);\n };\n\n const canSubmit = !!String(resolvedValue).trim() && !disabled && !loading;\n\n const handleSubmit = () => {\n const text = String(resolvedValue).trim();\n if (!text || !canSubmit) return;\n onSubmit?.(text);\n if (!isControlled) {\n setInternalValue(\"\");\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n onKeyDown?.(e);\n };\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n const useCustomAttachmentPreviews = attachmentPreviews !== undefined;\n const customAttachmentStrip = useCustomAttachmentPreviews ? attachmentPreviews : null;\n const defaultAttachmentStrip =\n !useCustomAttachmentPreviews && !!attachments?.length ? (\n <ChatInputDefaultAttachmentThumbnails\n attachments={attachments ?? []}\n disabled={disabled}\n onAttachmentRemove={onAttachmentRemove}\n />\n ) : null;\n const resolvedAttachmentStrip = customAttachmentStrip ?? defaultAttachmentStrip;\n const hasAttachmentStrip = resolvedAttachmentStrip != null;\n\n const selectedOption =\n selectOptions?.find((o) => o.value === selectValue) ?? selectOptions?.[0];\n const resolvedToolbarRight =\n toolbarRight ??\n (selectOptions && selectOptions.length > 0 ? (\n <InlineSelect\n options={selectOptions}\n value={selectValue}\n onChange={onSelectChange}\n disabled={disabled}\n selectedOption={selectedOption}\n />\n ) : null);\n\n return (\n <div\n className={cn(\n \"relative flex flex-col gap-6 rounded-lg border border-border-primary bg-surface-primary\",\n \"has-focus-visible:outline-none\",\n \"motion-safe:transition-colors\",\n disabled && \"opacity-50\",\n className,\n )}\n >\n <div className=\"flex flex-col\">\n {hasAttachmentStrip ? (\n <div className=\"flex gap-2 overflow-x-auto px-4 pt-4 pb-2 [scrollbar-width:thin]\">\n {resolvedAttachmentStrip}\n </div>\n ) : null}\n <textarea\n {...textareaProps}\n ref={mergedRef}\n value={isControlled ? value : internalValue}\n placeholder={placeholder}\n maxLength={maxLength}\n disabled={disabled}\n aria-label={ariaLabel ?? placeholder}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n rows={minRows}\n className={cn(\n \"w-full resize-none bg-transparent px-4\",\n hasAttachmentStrip ? \"pt-0\" : \"pt-4\",\n \"typography-body-small-14px-regular text-content-primary\",\n \"placeholder:text-content-tertiary\",\n \"focus:outline-none disabled:cursor-not-allowed\",\n \"overflow-y-auto\",\n )}\n style={{\n minHeight: `${minHeight}px`,\n maxHeight: `${maxHeight}px`,\n ...style,\n }}\n />\n </div>\n\n <div className=\"flex items-center justify-between gap-2 px-4 pb-4\">\n <div className=\"flex items-center gap-1\">\n {showFileButton && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<AddIcon />}\n aria-label={fileButtonAriaLabel}\n onClick={onFileClick}\n disabled={disabled}\n className=\"sm:border sm:border-border-primary max-sm:-ml-2\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-1\">\n {resolvedToolbarRight}\n <IconButton\n variant=\"primary\"\n size=\"32\"\n icon={submitIcon ?? <ArrowUpIcon />}\n aria-label={submitAriaLabel}\n onClick={handleSubmit}\n disabled={!canSubmit}\n className=\"disabled:bg-surface-secondary disabled:opacity-100 disabled:text-icons-primary\"\n />\n </div>\n </div>\n </div>\n );\n },\n);\n\nChatInput.displayName = \"ChatInput\";\n\ninterface InlineSelectProps {\n options: ChatInputSelectOption[];\n value?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n selectedOption?: ChatInputSelectOption;\n}\n\nfunction InlineSelect({ options, value, onChange, disabled, selectedOption }: InlineSelectProps) {\n const [open, setOpen] = React.useState(false);\n const containerRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const handleClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handleClick);\n return () => document.removeEventListener(\"mousedown\", handleClick);\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", handleKey);\n return () => document.removeEventListener(\"keydown\", handleKey);\n }, [open]);\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-label=\"Select model\"\n disabled={disabled}\n onClick={() => setOpen((prev) => !prev)}\n className={cn(\n \"typography-description-12px-semibold text-content-primary\",\n \"flex items-center gap-1 rounded-md px-2 py-2\",\n \"hover:bg-neutral-alphas-50 focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"motion-safe:transition-colors\",\n )}\n >\n {selectedOption?.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{selectedOption.icon}</span>\n )}\n {selectedOption?.label ?? options[0]?.label ?? \"Select\"}\n <ChevronDownIcon\n className={cn(\"size-4 motion-safe:transition-transform\", open && \"rotate-180\")}\n />\n </button>\n\n {open && (\n <div\n role=\"listbox\"\n className={cn(\n \"absolute right-0 bottom-full z-10 mb-1 min-w-[140px]\",\n \"overflow-hidden rounded-xs border border-border-primary bg-surface-primary p-1 shadow-lg\",\n )}\n >\n {options.map((option) => (\n <div\n key={option.value}\n role=\"option\"\n tabIndex={0}\n aria-selected={option.value === value}\n className={cn(\n \"typography-body-small-14px-regular flex cursor-pointer items-center gap-2 rounded-xs px-3 py-1.5\",\n \"text-content-primary hover:bg-neutral-alphas-50\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n option.value === value && \"bg-neutral-alphas-50\",\n )}\n onClick={() => {\n onChange?.(option.value);\n setOpen(false);\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onChange?.(option.value);\n setOpen(false);\n }\n }}\n >\n {option.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{option.icon}</span>\n )}\n {option.label}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":["jsxs","jsx","IconButton","CloseIcon","React","minHeight","maxHeight","cn","AddIcon","ArrowUpIcon","ChevronDownIcon"],"mappings":"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"}
1
+ {"version":3,"file":"ChatInput.cjs","sources":["../../../../src/components/ChatInput/ChatInput.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { AddIcon } from \"../Icons/AddIcon\";\nimport { ArrowUpIcon } from \"../Icons/ArrowUpIcon\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** A single image thumbnail in the built-in attachment strip. */\nexport interface ChatInputAttachmentItem {\n /** Stable id passed to {@link ChatInputProps.onAttachmentRemove} and used as React `key`. */\n id: string;\n /** Image URL for the thumbnail. */\n src: string;\n /** Optional value passed to the remove control `aria-label`. */\n ariaLabel?: string;\n}\n\n/** A single option for the inline model/dropdown selector. */\nexport interface ChatInputSelectOption {\n /** Unique value for this option. */\n value: string;\n /** Display label. */\n label: string;\n /** Optional icon rendered to the left of the label. */\n icon?: React.ReactNode;\n}\n\n/**\n * Props for {@link ChatInput}. Standard textarea HTML attributes are forwarded to the inner\n * `<textarea>` except `className` (applied to the outer container), `rows` (use `minRows`), and\n * `onSubmit` (replaced by the chat message submit callback).\n */\nexport interface ChatInputProps\n extends Omit<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n \"className\" | \"rows\" | \"onSubmit\"\n > {\n /** Minimum number of visible rows. @default 1 */\n minRows?: number;\n /** Maximum number of visible rows before scrolling. @default 6 */\n maxRows?: number;\n /** Whether a submission is in progress (disables submit, shows visual feedback). @default false */\n loading?: boolean;\n /**\n * Callback fired when the user submits (clicks the send button or presses Enter without Shift).\n * Receives the current trimmed text value.\n */\n onSubmit?: (value: string) => void;\n /**\n * When `true`, renders an \"attach file\" button in the bottom-left toolbar.\n * @default false\n */\n showFileButton?: boolean;\n /** Callback fired when the attach-file button is clicked. Only relevant when `showFileButton` is `true`. */\n onFileClick?: () => void;\n /** Accessible label for the attach-file button. @default \"Attach file\" */\n fileButtonAriaLabel?: string;\n /** Accessible label for the submit button. @default \"Send message\" */\n submitAriaLabel?: string;\n /** Icon element for the submit button. @default `<ArrowUpIcon />` */\n submitIcon?: React.ReactNode;\n /**\n * Optional content rendered in the bottom-right toolbar, to the left of the submit button.\n * When provided, takes precedence over the built-in `selectOptions` dropdown.\n */\n toolbarRight?: React.ReactNode;\n /**\n * Options for the built-in inline dropdown selector (e.g. model picker).\n * Ignored when `toolbarRight` is provided.\n */\n selectOptions?: ChatInputSelectOption[];\n /** Currently selected value for the built-in dropdown. Should match one of `selectOptions[].value`. */\n selectValue?: string;\n /** When `true`, disables only the built-in dropdown selector. @default false */\n selectDisabled?: boolean;\n /** Callback fired when the user picks a different dropdown option. */\n onSelectChange?: (value: string) => void;\n /**\n * Image attachments shown in the built-in thumbnail strip. Ignored when {@link ChatInputProps.attachmentPreviews}\n * is provided (including `null`).\n */\n attachments?: ChatInputAttachmentItem[];\n /**\n * Called when the user removes a built-in thumbnail. The remove button is disabled when this is\n * omitted or the input is {@link ChatInputProps.disabled}.\n */\n onAttachmentRemove?: (id: string) => void;\n /**\n * Replaces the built-in attachment strip entirely. When set to any value other than `undefined`\n * (including `null` or `[]`), {@link ChatInputProps.attachments} is ignored.\n */\n attachmentPreviews?: React.ReactNode;\n /** Additional className applied to the outermost container. */\n className?: string;\n}\n\nconst LINE_HEIGHT = 18;\nconst TEXTAREA_PY = 12;\n\nfunction calculateHeight(rows: number): number {\n return LINE_HEIGHT * rows + TEXTAREA_PY * 2;\n}\n\ninterface ChatInputDefaultAttachmentThumbnailsProps {\n attachments: ChatInputAttachmentItem[];\n onAttachmentRemove?: (id: string) => void;\n disabled?: boolean;\n}\n\nfunction ChatInputDefaultAttachmentThumbnails({\n attachments,\n onAttachmentRemove,\n disabled = false,\n}: ChatInputDefaultAttachmentThumbnailsProps) {\n return attachments.map((item) => (\n <div\n key={item.id}\n className=\"relative size-16 shrink-0 overflow-hidden rounded-sm border border-neutral-200 bg-background-secondary\"\n >\n <img src={item.src} alt=\"\" className=\"size-full object-cover\" />\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={item.ariaLabel ? `Remove ${item.ariaLabel}` : \"Remove attachment\"}\n icon={<CloseIcon className=\"!size-3\" />}\n disabled={disabled || !onAttachmentRemove}\n onClick={() => onAttachmentRemove?.(item.id)}\n className=\"absolute top-0.5 right-0.5 size-5 bg-neutral-900/40 p-1 text-white hover:bg-neutral-900/55\"\n />\n </div>\n ));\n}\n\n/**\n * A chat-style multi-line input with an integrated toolbar containing an\n * optional file-attach button, optional right-side controls (e.g. a model\n * selector), and a submit button — all inside a single bordered container.\n *\n * Designed to behave like modern AI chat inputs: auto-grows with content,\n * submits on Enter (Shift+Enter for newlines), and keeps controls inline.\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Type a message...\"\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Ask the agent...\"\n * showFileButton\n * onFileClick={() => openFilePicker()}\n * selectOptions={[\n * { value: \"fanvue-ai\", label: \"Fanvue AI\", icon: <AIIcon className=\"size-4\" /> },\n * { value: \"example\", label: \"Example\", icon: <BulbIcon className=\"size-4\" /> },\n * ]}\n * selectValue=\"fanvue-ai\"\n * onSelectChange={(v) => setModel(v)}\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachments={files}\n * onAttachmentRemove={(id) => setFiles((prev) => prev.filter((f) => f.id !== id))}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachmentPreviews={<CustomVideoStrip items={items} />}\n * />\n * ```\n */\nexport const ChatInput = React.forwardRef<HTMLTextAreaElement, ChatInputProps>(\n (\n {\n className,\n minRows = 1,\n maxRows = 6,\n disabled = false,\n loading = false,\n value,\n defaultValue,\n placeholder,\n maxLength,\n \"aria-label\": ariaLabel,\n onChange,\n onKeyDown,\n onSubmit,\n showFileButton = false,\n onFileClick,\n fileButtonAriaLabel = \"Attach file\",\n submitAriaLabel = \"Send message\",\n submitIcon,\n toolbarRight,\n selectOptions,\n selectValue,\n selectDisabled = false,\n onSelectChange,\n attachments,\n onAttachmentRemove,\n attachmentPreviews,\n style,\n ...textareaProps\n },\n ref,\n ) => {\n const internalRef = React.useRef<HTMLTextAreaElement>(null);\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const resolvedValue = value !== undefined ? value : internalValue;\n const isControlled = value !== undefined;\n\n const mergedRef = React.useCallback(\n (node: HTMLTextAreaElement | null) => {\n (internalRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n }\n },\n [ref],\n );\n\n const adjustHeight = React.useCallback(() => {\n const textarea = internalRef.current;\n if (!textarea) return;\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n textarea.style.height = `${minHeight}px`;\n const desired = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);\n textarea.style.height = `${desired}px`;\n }, [minRows, maxRows]);\n\n React.useEffect(() => {\n adjustHeight();\n }, [resolvedValue, adjustHeight]);\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (!isControlled) {\n setInternalValue(e.target.value);\n }\n onChange?.(e);\n };\n\n const canSubmit = !!String(resolvedValue).trim() && !disabled && !loading;\n\n const handleSubmit = () => {\n const text = String(resolvedValue).trim();\n if (!text || !canSubmit) return;\n onSubmit?.(text);\n if (!isControlled) {\n setInternalValue(\"\");\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n onKeyDown?.(e);\n };\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n const useCustomAttachmentPreviews = attachmentPreviews !== undefined;\n const customAttachmentStrip = useCustomAttachmentPreviews ? attachmentPreviews : null;\n const defaultAttachmentStrip =\n !useCustomAttachmentPreviews && !!attachments?.length ? (\n <ChatInputDefaultAttachmentThumbnails\n attachments={attachments ?? []}\n disabled={disabled}\n onAttachmentRemove={onAttachmentRemove}\n />\n ) : null;\n const resolvedAttachmentStrip = customAttachmentStrip ?? defaultAttachmentStrip;\n const hasAttachmentStrip = resolvedAttachmentStrip != null;\n\n const selectedOption =\n selectOptions?.find((o) => o.value === selectValue) ?? selectOptions?.[0];\n const resolvedToolbarRight =\n toolbarRight ??\n (selectOptions && selectOptions.length > 0 ? (\n <InlineSelect\n options={selectOptions}\n value={selectValue}\n onChange={onSelectChange}\n disabled={disabled || selectDisabled}\n selectedOption={selectedOption}\n />\n ) : null);\n\n return (\n <div\n className={cn(\n \"relative flex flex-col gap-6 rounded-lg border border-border-primary bg-surface-primary\",\n \"has-focus-visible:outline-none\",\n \"motion-safe:transition-colors\",\n disabled && \"opacity-50\",\n className,\n )}\n >\n <div className=\"flex flex-col\">\n {hasAttachmentStrip ? (\n <div className=\"flex gap-2 overflow-x-auto px-4 pt-4 pb-2 [scrollbar-width:thin]\">\n {resolvedAttachmentStrip}\n </div>\n ) : null}\n <textarea\n {...textareaProps}\n ref={mergedRef}\n value={isControlled ? value : internalValue}\n placeholder={placeholder}\n maxLength={maxLength}\n disabled={disabled}\n aria-label={ariaLabel ?? placeholder}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n rows={minRows}\n className={cn(\n \"w-full resize-none bg-transparent px-4\",\n hasAttachmentStrip ? \"pt-0\" : \"pt-4\",\n \"typography-body-small-14px-regular text-content-primary\",\n \"placeholder:text-content-tertiary\",\n \"focus:outline-none disabled:cursor-not-allowed\",\n \"overflow-y-auto\",\n )}\n style={{\n minHeight: `${minHeight}px`,\n maxHeight: `${maxHeight}px`,\n ...style,\n }}\n />\n </div>\n\n <div className=\"flex items-center justify-between gap-2 px-4 pb-4\">\n <div className=\"flex items-center gap-1\">\n {showFileButton && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<AddIcon />}\n aria-label={fileButtonAriaLabel}\n onClick={onFileClick}\n disabled={disabled}\n className=\"sm:border sm:border-border-primary max-sm:-ml-2\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-1\">\n {resolvedToolbarRight}\n <IconButton\n variant=\"primary\"\n size=\"32\"\n icon={submitIcon ?? <ArrowUpIcon />}\n aria-label={submitAriaLabel}\n onClick={handleSubmit}\n disabled={!canSubmit}\n className=\"disabled:bg-surface-secondary disabled:opacity-100 disabled:text-icons-primary\"\n />\n </div>\n </div>\n </div>\n );\n },\n);\n\nChatInput.displayName = \"ChatInput\";\n\ninterface InlineSelectProps {\n options: ChatInputSelectOption[];\n value?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n selectedOption?: ChatInputSelectOption;\n}\n\nfunction InlineSelect({ options, value, onChange, disabled, selectedOption }: InlineSelectProps) {\n const [open, setOpen] = React.useState(false);\n const containerRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const handleClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handleClick);\n return () => document.removeEventListener(\"mousedown\", handleClick);\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", handleKey);\n return () => document.removeEventListener(\"keydown\", handleKey);\n }, [open]);\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-label=\"Select model\"\n disabled={disabled}\n onClick={() => setOpen((prev) => !prev)}\n className={cn(\n \"typography-description-12px-semibold text-content-primary\",\n \"flex items-center gap-1 rounded-md px-2 py-2\",\n \"hover:bg-neutral-alphas-50 focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"motion-safe:transition-colors\",\n )}\n >\n {selectedOption?.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{selectedOption.icon}</span>\n )}\n {selectedOption?.label ?? options[0]?.label ?? \"Select\"}\n <ChevronDownIcon\n className={cn(\"size-4 motion-safe:transition-transform\", open && \"rotate-180\")}\n />\n </button>\n\n {open && (\n <div\n role=\"listbox\"\n className={cn(\n \"absolute right-0 bottom-full z-10 mb-1 min-w-[140px]\",\n \"overflow-hidden rounded-xs border border-border-primary bg-surface-primary p-1 shadow-lg\",\n )}\n >\n {options.map((option) => (\n <div\n key={option.value}\n role=\"option\"\n tabIndex={0}\n aria-selected={option.value === value}\n className={cn(\n \"typography-body-small-14px-regular flex cursor-pointer items-center gap-2 rounded-xs px-3 py-1.5\",\n \"text-content-primary hover:bg-neutral-alphas-50\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n option.value === value && \"bg-neutral-alphas-50\",\n )}\n onClick={() => {\n onChange?.(option.value);\n setOpen(false);\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onChange?.(option.value);\n setOpen(false);\n }\n }}\n >\n {option.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{option.icon}</span>\n )}\n {option.label}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":["jsxs","jsx","IconButton","CloseIcon","React","minHeight","maxHeight","cn","AddIcon","ArrowUpIcon","ChevronDownIcon"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiGA,MAAM,cAAc;AACpB,MAAM,cAAc;AAEpB,SAAS,gBAAgB,MAAsB;AAC7C,SAAO,cAAc,OAAO,cAAc;AAC5C;AAQA,SAAS,qCAAqC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAA8C;AAC5C,SAAO,YAAY,IAAI,CAAC,SACtBA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MAEC,WAAU;AAAA,MAEV,UAAA;AAAA,QAAAC,+BAAC,SAAI,KAAK,KAAK,KAAK,KAAI,IAAG,WAAU,0BAAyB;AAAA,QAC9DA,2BAAAA;AAAAA,UAACC,WAAAA;AAAAA,UAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,cAAY,KAAK,YAAY,UAAU,KAAK,SAAS,KAAK;AAAA,YAC1D,MAAMD,2BAAAA,IAACE,UAAAA,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,YACrC,UAAU,YAAY,CAAC;AAAA,YACvB,SAAS,MAAM,qBAAqB,KAAK,EAAE;AAAA,YAC3C,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACZ;AAAA,IAAA;AAAA,IAZK,KAAK;AAAA,EAAA,CAcb;AACH;AAqDO,MAAM,YAAYC,iBAAM;AAAA,EAC7B,CACE;AAAA,IACE;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA,sBAAsB;AAAA,IACtB,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,cAAcA,iBAAM,OAA4B,IAAI;AAC1D,UAAM,CAAC,eAAe,gBAAgB,IAAIA,iBAAM,SAAS,gBAAgB,EAAE;AAC3E,UAAM,gBAAgB,UAAU,SAAY,QAAQ;AACpD,UAAM,eAAe,UAAU;AAE/B,UAAM,YAAYA,iBAAM;AAAA,MACtB,CAAC,SAAqC;AACnC,oBAAmE,UAAU;AAC9E,YAAI,OAAO,QAAQ,YAAY;AAC7B,cAAI,IAAI;AAAA,QACV,WAAW,KAAK;AACb,cAA2D,UAAU;AAAA,QACxE;AAAA,MACF;AAAA,MACA,CAAC,GAAG;AAAA,IAAA;AAGN,UAAM,eAAeA,iBAAM,YAAY,MAAM;AAC3C,YAAM,WAAW,YAAY;AAC7B,UAAI,CAAC,SAAU;AAEf,YAAMC,aAAY,gBAAgB,OAAO;AACzC,YAAMC,aAAY,gBAAgB,OAAO;AAEzC,eAAS,MAAM,SAAS,GAAGD,UAAS;AACpC,YAAM,UAAU,KAAK,IAAI,KAAK,IAAI,SAAS,cAAcA,UAAS,GAAGC,UAAS;AAC9E,eAAS,MAAM,SAAS,GAAG,OAAO;AAAA,IACpC,GAAG,CAAC,SAAS,OAAO,CAAC;AAErBF,qBAAM,UAAU,MAAM;AACpB,mBAAA;AAAA,IACF,GAAG,CAAC,eAAe,YAAY,CAAC;AAEhC,UAAM,eAAe,CAAC,MAA8C;AAClE,UAAI,CAAC,cAAc;AACjB,yBAAiB,EAAE,OAAO,KAAK;AAAA,MACjC;AACA,iBAAW,CAAC;AAAA,IACd;AAEA,UAAM,YAAY,CAAC,CAAC,OAAO,aAAa,EAAE,KAAA,KAAU,CAAC,YAAY,CAAC;AAElE,UAAM,eAAe,MAAM;AACzB,YAAM,OAAO,OAAO,aAAa,EAAE,KAAA;AACnC,UAAI,CAAC,QAAQ,CAAC,UAAW;AACzB,iBAAW,IAAI;AACf,UAAI,CAAC,cAAc;AACjB,yBAAiB,EAAE;AAAA,MACrB;AAAA,IACF;AAEA,UAAM,gBAAgB,CAAC,MAAgD;AACrE,UAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,UAAE,eAAA;AACF,qBAAA;AAAA,MACF;AACA,kBAAY,CAAC;AAAA,IACf;AAEA,UAAM,YAAY,gBAAgB,OAAO;AACzC,UAAM,YAAY,gBAAgB,OAAO;AAEzC,UAAM,8BAA8B,uBAAuB;AAC3D,UAAM,wBAAwB,8BAA8B,qBAAqB;AACjF,UAAM,yBACJ,CAAC,+BAA+B,CAAC,CAAC,aAAa,SAC7CH,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,aAAa,eAAe,CAAA;AAAA,QAC5B;AAAA,QACA;AAAA,MAAA;AAAA,IAAA,IAEA;AACN,UAAM,0BAA0B,yBAAyB;AACzD,UAAM,qBAAqB,2BAA2B;AAEtD,UAAM,iBACJ,eAAe,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW,KAAK,gBAAgB,CAAC;AAC1E,UAAM,uBACJ,iBACC,iBAAiB,cAAc,SAAS,IACvCA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAS;AAAA,QACT,OAAO;AAAA,QACP,UAAU;AAAA,QACV,UAAU,YAAY;AAAA,QACtB;AAAA,MAAA;AAAA,IAAA,IAEA;AAEN,WACED,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAWO,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAGF,UAAA;AAAA,UAAAP,2BAAAA,KAAC,OAAA,EAAI,WAAU,iBACZ,UAAA;AAAA,YAAA,qBACCC,2BAAAA,IAAC,OAAA,EAAI,WAAU,oEACZ,mCACH,IACE;AAAA,YACJA,2BAAAA;AAAAA,cAAC;AAAA,cAAA;AAAA,gBACE,GAAG;AAAA,gBACJ,KAAK;AAAA,gBACL,OAAO,eAAe,QAAQ;AAAA,gBAC9B;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,cAAY,aAAa;AAAA,gBACzB,UAAU;AAAA,gBACV,WAAW;AAAA,gBACX,MAAM;AAAA,gBACN,WAAWM,GAAAA;AAAAA,kBACT;AAAA,kBACA,qBAAqB,SAAS;AAAA,kBAC9B;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,gBAAA;AAAA,gBAEF,OAAO;AAAA,kBACL,WAAW,GAAG,SAAS;AAAA,kBACvB,WAAW,GAAG,SAAS;AAAA,kBACvB,GAAG;AAAA,gBAAA;AAAA,cACL;AAAA,YAAA;AAAA,UACF,GACF;AAAA,UAEAP,2BAAAA,KAAC,OAAA,EAAI,WAAU,qDACb,UAAA;AAAA,YAAAC,2BAAAA,IAAC,OAAA,EAAI,WAAU,2BACZ,UAAA,kBACCA,2BAAAA;AAAAA,cAACC,WAAAA;AAAAA,cAAA;AAAA,gBACC,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,qCAAOM,QAAAA,SAAA,EAAQ;AAAA,gBACf,cAAY;AAAA,gBACZ,SAAS;AAAA,gBACT;AAAA,gBACA,WAAU;AAAA,cAAA;AAAA,YAAA,GAGhB;AAAA,YAEAR,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,cAAA;AAAA,cACDC,2BAAAA;AAAAA,gBAACC,WAAAA;AAAAA,gBAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,MAAM,cAAcD,+BAACQ,YAAAA,aAAA,CAAA,CAAY;AAAA,kBACjC,cAAY;AAAA,kBACZ,SAAS;AAAA,kBACT,UAAU,CAAC;AAAA,kBACX,WAAU;AAAA,gBAAA;AAAA,cAAA;AAAA,YACZ,EAAA,CACF;AAAA,UAAA,EAAA,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;AAEA,UAAU,cAAc;AAUxB,SAAS,aAAa,EAAE,SAAS,OAAO,UAAU,UAAU,kBAAqC;AAC/F,QAAM,CAAC,MAAM,OAAO,IAAIL,iBAAM,SAAS,KAAK;AAC5C,QAAM,eAAeA,iBAAM,OAAuB,IAAI;AAEtDA,mBAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,cAAc,CAAC,MAAkB;AACrC,UAAI,aAAa,WAAW,CAAC,aAAa,QAAQ,SAAS,EAAE,MAAc,GAAG;AAC5E,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF;AACA,aAAS,iBAAiB,aAAa,WAAW;AAClD,WAAO,MAAM,SAAS,oBAAoB,aAAa,WAAW;AAAA,EACpE,GAAG,CAAC,IAAI,CAAC;AAETA,mBAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,SAAU,SAAQ,KAAK;AAAA,IACvC;AACA,aAAS,iBAAiB,WAAW,SAAS;AAC9C,WAAO,MAAM,SAAS,oBAAoB,WAAW,SAAS;AAAA,EAChE,GAAG,CAAC,IAAI,CAAC;AAET,SACEJ,2BAAAA,KAAC,OAAA,EAAI,KAAK,cAAc,WAAU,YAChC,UAAA;AAAA,IAAAA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,iBAAe;AAAA,QACf,iBAAc;AAAA,QACd,cAAW;AAAA,QACX;AAAA,QACA,SAAS,MAAM,QAAQ,CAAC,SAAS,CAAC,IAAI;AAAA,QACtC,WAAWO,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGD,UAAA;AAAA,UAAA,gBAAgB,QACfN,2BAAAA,IAAC,QAAA,EAAK,WAAU,6CAA6C,yBAAe,MAAK;AAAA,UAElF,gBAAgB,SAAS,QAAQ,CAAC,GAAG,SAAS;AAAA,UAC/CA,2BAAAA;AAAAA,YAACS,gBAAAA;AAAAA,YAAA;AAAA,cACC,WAAWH,GAAAA,GAAG,2CAA2C,QAAQ,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,QAC/E;AAAA,MAAA;AAAA,IAAA;AAAA,IAGD,QACCN,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAWM,GAAAA;AAAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAGD,UAAA,QAAQ,IAAI,CAAC,WACZP,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YAEC,MAAK;AAAA,YACL,UAAU;AAAA,YACV,iBAAe,OAAO,UAAU;AAAA,YAChC,WAAWO,GAAAA;AAAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,cACA,OAAO,UAAU,SAAS;AAAA,YAAA;AAAA,YAE5B,SAAS,MAAM;AACb,yBAAW,OAAO,KAAK;AACvB,sBAAQ,KAAK;AAAA,YACf;AAAA,YACA,WAAW,CAAC,MAAM;AAChB,kBAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,kBAAE,eAAA;AACF,2BAAW,OAAO,KAAK;AACvB,wBAAQ,KAAK;AAAA,cACf;AAAA,YACF;AAAA,YAEC,UAAA;AAAA,cAAA,OAAO,QACNN,2BAAAA,IAAC,QAAA,EAAK,WAAU,6CAA6C,iBAAO,MAAK;AAAA,cAE1E,OAAO;AAAA,YAAA;AAAA,UAAA;AAAA,UAzBH,OAAO;AAAA,QAAA,CA2Bf;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GAEJ;AAEJ;;"}
@@ -63,6 +63,7 @@ const ChatInput = React.forwardRef(
63
63
  toolbarRight,
64
64
  selectOptions,
65
65
  selectValue,
66
+ selectDisabled = false,
66
67
  onSelectChange,
67
68
  attachments,
68
69
  onAttachmentRemove,
@@ -140,7 +141,7 @@ const ChatInput = React.forwardRef(
140
141
  options: selectOptions,
141
142
  value: selectValue,
142
143
  onChange: onSelectChange,
143
- disabled,
144
+ disabled: disabled || selectDisabled,
144
145
  selectedOption
145
146
  }
146
147
  ) : null);
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInput.mjs","sources":["../../../src/components/ChatInput/ChatInput.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { AddIcon } from \"../Icons/AddIcon\";\nimport { ArrowUpIcon } from \"../Icons/ArrowUpIcon\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** A single image thumbnail in the built-in attachment strip. */\nexport interface ChatInputAttachmentItem {\n /** Stable id passed to {@link ChatInputProps.onAttachmentRemove} and used as React `key`. */\n id: string;\n /** Image URL for the thumbnail. */\n src: string;\n /** Optional value passed to the remove control `aria-label`. */\n ariaLabel?: string;\n}\n\n/** A single option for the inline model/dropdown selector. */\nexport interface ChatInputSelectOption {\n /** Unique value for this option. */\n value: string;\n /** Display label. */\n label: string;\n /** Optional icon rendered to the left of the label. */\n icon?: React.ReactNode;\n}\n\n/**\n * Props for {@link ChatInput}. Standard textarea HTML attributes are forwarded to the inner\n * `<textarea>` except `className` (applied to the outer container), `rows` (use `minRows`), and\n * `onSubmit` (replaced by the chat message submit callback).\n */\nexport interface ChatInputProps\n extends Omit<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n \"className\" | \"rows\" | \"onSubmit\"\n > {\n /** Minimum number of visible rows. @default 1 */\n minRows?: number;\n /** Maximum number of visible rows before scrolling. @default 6 */\n maxRows?: number;\n /** Whether a submission is in progress (disables submit, shows visual feedback). @default false */\n loading?: boolean;\n /**\n * Callback fired when the user submits (clicks the send button or presses Enter without Shift).\n * Receives the current trimmed text value.\n */\n onSubmit?: (value: string) => void;\n /**\n * When `true`, renders an \"attach file\" button in the bottom-left toolbar.\n * @default false\n */\n showFileButton?: boolean;\n /** Callback fired when the attach-file button is clicked. Only relevant when `showFileButton` is `true`. */\n onFileClick?: () => void;\n /** Accessible label for the attach-file button. @default \"Attach file\" */\n fileButtonAriaLabel?: string;\n /** Accessible label for the submit button. @default \"Send message\" */\n submitAriaLabel?: string;\n /** Icon element for the submit button. @default `<ArrowUpIcon />` */\n submitIcon?: React.ReactNode;\n /**\n * Optional content rendered in the bottom-right toolbar, to the left of the submit button.\n * When provided, takes precedence over the built-in `selectOptions` dropdown.\n */\n toolbarRight?: React.ReactNode;\n /**\n * Options for the built-in inline dropdown selector (e.g. model picker).\n * Ignored when `toolbarRight` is provided.\n */\n selectOptions?: ChatInputSelectOption[];\n /** Currently selected value for the built-in dropdown. Should match one of `selectOptions[].value`. */\n selectValue?: string;\n /** Callback fired when the user picks a different dropdown option. */\n onSelectChange?: (value: string) => void;\n /**\n * Image attachments shown in the built-in thumbnail strip. Ignored when {@link ChatInputProps.attachmentPreviews}\n * is provided (including `null`).\n */\n attachments?: ChatInputAttachmentItem[];\n /**\n * Called when the user removes a built-in thumbnail. The remove button is disabled when this is\n * omitted or the input is {@link ChatInputProps.disabled}.\n */\n onAttachmentRemove?: (id: string) => void;\n /**\n * Replaces the built-in attachment strip entirely. When set to any value other than `undefined`\n * (including `null` or `[]`), {@link ChatInputProps.attachments} is ignored.\n */\n attachmentPreviews?: React.ReactNode;\n /** Additional className applied to the outermost container. */\n className?: string;\n}\n\nconst LINE_HEIGHT = 18;\nconst TEXTAREA_PY = 12;\n\nfunction calculateHeight(rows: number): number {\n return LINE_HEIGHT * rows + TEXTAREA_PY * 2;\n}\n\ninterface ChatInputDefaultAttachmentThumbnailsProps {\n attachments: ChatInputAttachmentItem[];\n onAttachmentRemove?: (id: string) => void;\n disabled?: boolean;\n}\n\nfunction ChatInputDefaultAttachmentThumbnails({\n attachments,\n onAttachmentRemove,\n disabled = false,\n}: ChatInputDefaultAttachmentThumbnailsProps) {\n return attachments.map((item) => (\n <div\n key={item.id}\n className=\"relative size-16 shrink-0 overflow-hidden rounded-sm border border-neutral-200 bg-background-secondary\"\n >\n <img src={item.src} alt=\"\" className=\"size-full object-cover\" />\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={item.ariaLabel ? `Remove ${item.ariaLabel}` : \"Remove attachment\"}\n icon={<CloseIcon className=\"!size-3\" />}\n disabled={disabled || !onAttachmentRemove}\n onClick={() => onAttachmentRemove?.(item.id)}\n className=\"absolute top-0.5 right-0.5 size-5 bg-neutral-900/40 p-1 text-white hover:bg-neutral-900/55\"\n />\n </div>\n ));\n}\n\n/**\n * A chat-style multi-line input with an integrated toolbar containing an\n * optional file-attach button, optional right-side controls (e.g. a model\n * selector), and a submit button — all inside a single bordered container.\n *\n * Designed to behave like modern AI chat inputs: auto-grows with content,\n * submits on Enter (Shift+Enter for newlines), and keeps controls inline.\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Type a message...\"\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Ask the agent...\"\n * showFileButton\n * onFileClick={() => openFilePicker()}\n * selectOptions={[\n * { value: \"fanvue-ai\", label: \"Fanvue AI\", icon: <AIIcon className=\"size-4\" /> },\n * { value: \"example\", label: \"Example\", icon: <BulbIcon className=\"size-4\" /> },\n * ]}\n * selectValue=\"fanvue-ai\"\n * onSelectChange={(v) => setModel(v)}\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachments={files}\n * onAttachmentRemove={(id) => setFiles((prev) => prev.filter((f) => f.id !== id))}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachmentPreviews={<CustomVideoStrip items={items} />}\n * />\n * ```\n */\nexport const ChatInput = React.forwardRef<HTMLTextAreaElement, ChatInputProps>(\n (\n {\n className,\n minRows = 1,\n maxRows = 6,\n disabled = false,\n loading = false,\n value,\n defaultValue,\n placeholder,\n maxLength,\n \"aria-label\": ariaLabel,\n onChange,\n onKeyDown,\n onSubmit,\n showFileButton = false,\n onFileClick,\n fileButtonAriaLabel = \"Attach file\",\n submitAriaLabel = \"Send message\",\n submitIcon,\n toolbarRight,\n selectOptions,\n selectValue,\n onSelectChange,\n attachments,\n onAttachmentRemove,\n attachmentPreviews,\n style,\n ...textareaProps\n },\n ref,\n ) => {\n const internalRef = React.useRef<HTMLTextAreaElement>(null);\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const resolvedValue = value !== undefined ? value : internalValue;\n const isControlled = value !== undefined;\n\n const mergedRef = React.useCallback(\n (node: HTMLTextAreaElement | null) => {\n (internalRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n }\n },\n [ref],\n );\n\n const adjustHeight = React.useCallback(() => {\n const textarea = internalRef.current;\n if (!textarea) return;\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n textarea.style.height = `${minHeight}px`;\n const desired = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);\n textarea.style.height = `${desired}px`;\n }, [minRows, maxRows]);\n\n React.useEffect(() => {\n adjustHeight();\n }, [resolvedValue, adjustHeight]);\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (!isControlled) {\n setInternalValue(e.target.value);\n }\n onChange?.(e);\n };\n\n const canSubmit = !!String(resolvedValue).trim() && !disabled && !loading;\n\n const handleSubmit = () => {\n const text = String(resolvedValue).trim();\n if (!text || !canSubmit) return;\n onSubmit?.(text);\n if (!isControlled) {\n setInternalValue(\"\");\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n onKeyDown?.(e);\n };\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n const useCustomAttachmentPreviews = attachmentPreviews !== undefined;\n const customAttachmentStrip = useCustomAttachmentPreviews ? attachmentPreviews : null;\n const defaultAttachmentStrip =\n !useCustomAttachmentPreviews && !!attachments?.length ? (\n <ChatInputDefaultAttachmentThumbnails\n attachments={attachments ?? []}\n disabled={disabled}\n onAttachmentRemove={onAttachmentRemove}\n />\n ) : null;\n const resolvedAttachmentStrip = customAttachmentStrip ?? defaultAttachmentStrip;\n const hasAttachmentStrip = resolvedAttachmentStrip != null;\n\n const selectedOption =\n selectOptions?.find((o) => o.value === selectValue) ?? selectOptions?.[0];\n const resolvedToolbarRight =\n toolbarRight ??\n (selectOptions && selectOptions.length > 0 ? (\n <InlineSelect\n options={selectOptions}\n value={selectValue}\n onChange={onSelectChange}\n disabled={disabled}\n selectedOption={selectedOption}\n />\n ) : null);\n\n return (\n <div\n className={cn(\n \"relative flex flex-col gap-6 rounded-lg border border-border-primary bg-surface-primary\",\n \"has-focus-visible:outline-none\",\n \"motion-safe:transition-colors\",\n disabled && \"opacity-50\",\n className,\n )}\n >\n <div className=\"flex flex-col\">\n {hasAttachmentStrip ? (\n <div className=\"flex gap-2 overflow-x-auto px-4 pt-4 pb-2 [scrollbar-width:thin]\">\n {resolvedAttachmentStrip}\n </div>\n ) : null}\n <textarea\n {...textareaProps}\n ref={mergedRef}\n value={isControlled ? value : internalValue}\n placeholder={placeholder}\n maxLength={maxLength}\n disabled={disabled}\n aria-label={ariaLabel ?? placeholder}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n rows={minRows}\n className={cn(\n \"w-full resize-none bg-transparent px-4\",\n hasAttachmentStrip ? \"pt-0\" : \"pt-4\",\n \"typography-body-small-14px-regular text-content-primary\",\n \"placeholder:text-content-tertiary\",\n \"focus:outline-none disabled:cursor-not-allowed\",\n \"overflow-y-auto\",\n )}\n style={{\n minHeight: `${minHeight}px`,\n maxHeight: `${maxHeight}px`,\n ...style,\n }}\n />\n </div>\n\n <div className=\"flex items-center justify-between gap-2 px-4 pb-4\">\n <div className=\"flex items-center gap-1\">\n {showFileButton && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<AddIcon />}\n aria-label={fileButtonAriaLabel}\n onClick={onFileClick}\n disabled={disabled}\n className=\"sm:border sm:border-border-primary max-sm:-ml-2\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-1\">\n {resolvedToolbarRight}\n <IconButton\n variant=\"primary\"\n size=\"32\"\n icon={submitIcon ?? <ArrowUpIcon />}\n aria-label={submitAriaLabel}\n onClick={handleSubmit}\n disabled={!canSubmit}\n className=\"disabled:bg-surface-secondary disabled:opacity-100 disabled:text-icons-primary\"\n />\n </div>\n </div>\n </div>\n );\n },\n);\n\nChatInput.displayName = \"ChatInput\";\n\ninterface InlineSelectProps {\n options: ChatInputSelectOption[];\n value?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n selectedOption?: ChatInputSelectOption;\n}\n\nfunction InlineSelect({ options, value, onChange, disabled, selectedOption }: InlineSelectProps) {\n const [open, setOpen] = React.useState(false);\n const containerRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const handleClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handleClick);\n return () => document.removeEventListener(\"mousedown\", handleClick);\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", handleKey);\n return () => document.removeEventListener(\"keydown\", handleKey);\n }, [open]);\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-label=\"Select model\"\n disabled={disabled}\n onClick={() => setOpen((prev) => !prev)}\n className={cn(\n \"typography-description-12px-semibold text-content-primary\",\n \"flex items-center gap-1 rounded-md px-2 py-2\",\n \"hover:bg-neutral-alphas-50 focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"motion-safe:transition-colors\",\n )}\n >\n {selectedOption?.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{selectedOption.icon}</span>\n )}\n {selectedOption?.label ?? options[0]?.label ?? \"Select\"}\n <ChevronDownIcon\n className={cn(\"size-4 motion-safe:transition-transform\", open && \"rotate-180\")}\n />\n </button>\n\n {open && (\n <div\n role=\"listbox\"\n className={cn(\n \"absolute right-0 bottom-full z-10 mb-1 min-w-[140px]\",\n \"overflow-hidden rounded-xs border border-border-primary bg-surface-primary p-1 shadow-lg\",\n )}\n >\n {options.map((option) => (\n <div\n key={option.value}\n role=\"option\"\n tabIndex={0}\n aria-selected={option.value === value}\n className={cn(\n \"typography-body-small-14px-regular flex cursor-pointer items-center gap-2 rounded-xs px-3 py-1.5\",\n \"text-content-primary hover:bg-neutral-alphas-50\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n option.value === value && \"bg-neutral-alphas-50\",\n )}\n onClick={() => {\n onChange?.(option.value);\n setOpen(false);\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onChange?.(option.value);\n setOpen(false);\n }\n }}\n >\n {option.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{option.icon}</span>\n )}\n {option.label}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":["minHeight","maxHeight"],"mappings":"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"}
1
+ {"version":3,"file":"ChatInput.mjs","sources":["../../../src/components/ChatInput/ChatInput.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { IconButton } from \"../IconButton/IconButton\";\nimport { AddIcon } from \"../Icons/AddIcon\";\nimport { ArrowUpIcon } from \"../Icons/ArrowUpIcon\";\nimport { ChevronDownIcon } from \"../Icons/ChevronDownIcon\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\n\n/** A single image thumbnail in the built-in attachment strip. */\nexport interface ChatInputAttachmentItem {\n /** Stable id passed to {@link ChatInputProps.onAttachmentRemove} and used as React `key`. */\n id: string;\n /** Image URL for the thumbnail. */\n src: string;\n /** Optional value passed to the remove control `aria-label`. */\n ariaLabel?: string;\n}\n\n/** A single option for the inline model/dropdown selector. */\nexport interface ChatInputSelectOption {\n /** Unique value for this option. */\n value: string;\n /** Display label. */\n label: string;\n /** Optional icon rendered to the left of the label. */\n icon?: React.ReactNode;\n}\n\n/**\n * Props for {@link ChatInput}. Standard textarea HTML attributes are forwarded to the inner\n * `<textarea>` except `className` (applied to the outer container), `rows` (use `minRows`), and\n * `onSubmit` (replaced by the chat message submit callback).\n */\nexport interface ChatInputProps\n extends Omit<\n React.TextareaHTMLAttributes<HTMLTextAreaElement>,\n \"className\" | \"rows\" | \"onSubmit\"\n > {\n /** Minimum number of visible rows. @default 1 */\n minRows?: number;\n /** Maximum number of visible rows before scrolling. @default 6 */\n maxRows?: number;\n /** Whether a submission is in progress (disables submit, shows visual feedback). @default false */\n loading?: boolean;\n /**\n * Callback fired when the user submits (clicks the send button or presses Enter without Shift).\n * Receives the current trimmed text value.\n */\n onSubmit?: (value: string) => void;\n /**\n * When `true`, renders an \"attach file\" button in the bottom-left toolbar.\n * @default false\n */\n showFileButton?: boolean;\n /** Callback fired when the attach-file button is clicked. Only relevant when `showFileButton` is `true`. */\n onFileClick?: () => void;\n /** Accessible label for the attach-file button. @default \"Attach file\" */\n fileButtonAriaLabel?: string;\n /** Accessible label for the submit button. @default \"Send message\" */\n submitAriaLabel?: string;\n /** Icon element for the submit button. @default `<ArrowUpIcon />` */\n submitIcon?: React.ReactNode;\n /**\n * Optional content rendered in the bottom-right toolbar, to the left of the submit button.\n * When provided, takes precedence over the built-in `selectOptions` dropdown.\n */\n toolbarRight?: React.ReactNode;\n /**\n * Options for the built-in inline dropdown selector (e.g. model picker).\n * Ignored when `toolbarRight` is provided.\n */\n selectOptions?: ChatInputSelectOption[];\n /** Currently selected value for the built-in dropdown. Should match one of `selectOptions[].value`. */\n selectValue?: string;\n /** When `true`, disables only the built-in dropdown selector. @default false */\n selectDisabled?: boolean;\n /** Callback fired when the user picks a different dropdown option. */\n onSelectChange?: (value: string) => void;\n /**\n * Image attachments shown in the built-in thumbnail strip. Ignored when {@link ChatInputProps.attachmentPreviews}\n * is provided (including `null`).\n */\n attachments?: ChatInputAttachmentItem[];\n /**\n * Called when the user removes a built-in thumbnail. The remove button is disabled when this is\n * omitted or the input is {@link ChatInputProps.disabled}.\n */\n onAttachmentRemove?: (id: string) => void;\n /**\n * Replaces the built-in attachment strip entirely. When set to any value other than `undefined`\n * (including `null` or `[]`), {@link ChatInputProps.attachments} is ignored.\n */\n attachmentPreviews?: React.ReactNode;\n /** Additional className applied to the outermost container. */\n className?: string;\n}\n\nconst LINE_HEIGHT = 18;\nconst TEXTAREA_PY = 12;\n\nfunction calculateHeight(rows: number): number {\n return LINE_HEIGHT * rows + TEXTAREA_PY * 2;\n}\n\ninterface ChatInputDefaultAttachmentThumbnailsProps {\n attachments: ChatInputAttachmentItem[];\n onAttachmentRemove?: (id: string) => void;\n disabled?: boolean;\n}\n\nfunction ChatInputDefaultAttachmentThumbnails({\n attachments,\n onAttachmentRemove,\n disabled = false,\n}: ChatInputDefaultAttachmentThumbnailsProps) {\n return attachments.map((item) => (\n <div\n key={item.id}\n className=\"relative size-16 shrink-0 overflow-hidden rounded-sm border border-neutral-200 bg-background-secondary\"\n >\n <img src={item.src} alt=\"\" className=\"size-full object-cover\" />\n <IconButton\n variant=\"tertiary\"\n size=\"24\"\n aria-label={item.ariaLabel ? `Remove ${item.ariaLabel}` : \"Remove attachment\"}\n icon={<CloseIcon className=\"!size-3\" />}\n disabled={disabled || !onAttachmentRemove}\n onClick={() => onAttachmentRemove?.(item.id)}\n className=\"absolute top-0.5 right-0.5 size-5 bg-neutral-900/40 p-1 text-white hover:bg-neutral-900/55\"\n />\n </div>\n ));\n}\n\n/**\n * A chat-style multi-line input with an integrated toolbar containing an\n * optional file-attach button, optional right-side controls (e.g. a model\n * selector), and a submit button — all inside a single bordered container.\n *\n * Designed to behave like modern AI chat inputs: auto-grows with content,\n * submits on Enter (Shift+Enter for newlines), and keeps controls inline.\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Type a message...\"\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * placeholder=\"Ask the agent...\"\n * showFileButton\n * onFileClick={() => openFilePicker()}\n * selectOptions={[\n * { value: \"fanvue-ai\", label: \"Fanvue AI\", icon: <AIIcon className=\"size-4\" /> },\n * { value: \"example\", label: \"Example\", icon: <BulbIcon className=\"size-4\" /> },\n * ]}\n * selectValue=\"fanvue-ai\"\n * onSelectChange={(v) => setModel(v)}\n * onSubmit={(text) => send(text)}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachments={files}\n * onAttachmentRemove={(id) => setFiles((prev) => prev.filter((f) => f.id !== id))}\n * />\n * ```\n *\n * @example\n * ```tsx\n * <ChatInput\n * showFileButton\n * onFileClick={() => openPicker()}\n * attachmentPreviews={<CustomVideoStrip items={items} />}\n * />\n * ```\n */\nexport const ChatInput = React.forwardRef<HTMLTextAreaElement, ChatInputProps>(\n (\n {\n className,\n minRows = 1,\n maxRows = 6,\n disabled = false,\n loading = false,\n value,\n defaultValue,\n placeholder,\n maxLength,\n \"aria-label\": ariaLabel,\n onChange,\n onKeyDown,\n onSubmit,\n showFileButton = false,\n onFileClick,\n fileButtonAriaLabel = \"Attach file\",\n submitAriaLabel = \"Send message\",\n submitIcon,\n toolbarRight,\n selectOptions,\n selectValue,\n selectDisabled = false,\n onSelectChange,\n attachments,\n onAttachmentRemove,\n attachmentPreviews,\n style,\n ...textareaProps\n },\n ref,\n ) => {\n const internalRef = React.useRef<HTMLTextAreaElement>(null);\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? \"\");\n const resolvedValue = value !== undefined ? value : internalValue;\n const isControlled = value !== undefined;\n\n const mergedRef = React.useCallback(\n (node: HTMLTextAreaElement | null) => {\n (internalRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n }\n },\n [ref],\n );\n\n const adjustHeight = React.useCallback(() => {\n const textarea = internalRef.current;\n if (!textarea) return;\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n textarea.style.height = `${minHeight}px`;\n const desired = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);\n textarea.style.height = `${desired}px`;\n }, [minRows, maxRows]);\n\n React.useEffect(() => {\n adjustHeight();\n }, [resolvedValue, adjustHeight]);\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (!isControlled) {\n setInternalValue(e.target.value);\n }\n onChange?.(e);\n };\n\n const canSubmit = !!String(resolvedValue).trim() && !disabled && !loading;\n\n const handleSubmit = () => {\n const text = String(resolvedValue).trim();\n if (!text || !canSubmit) return;\n onSubmit?.(text);\n if (!isControlled) {\n setInternalValue(\"\");\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n onKeyDown?.(e);\n };\n\n const minHeight = calculateHeight(minRows);\n const maxHeight = calculateHeight(maxRows);\n\n const useCustomAttachmentPreviews = attachmentPreviews !== undefined;\n const customAttachmentStrip = useCustomAttachmentPreviews ? attachmentPreviews : null;\n const defaultAttachmentStrip =\n !useCustomAttachmentPreviews && !!attachments?.length ? (\n <ChatInputDefaultAttachmentThumbnails\n attachments={attachments ?? []}\n disabled={disabled}\n onAttachmentRemove={onAttachmentRemove}\n />\n ) : null;\n const resolvedAttachmentStrip = customAttachmentStrip ?? defaultAttachmentStrip;\n const hasAttachmentStrip = resolvedAttachmentStrip != null;\n\n const selectedOption =\n selectOptions?.find((o) => o.value === selectValue) ?? selectOptions?.[0];\n const resolvedToolbarRight =\n toolbarRight ??\n (selectOptions && selectOptions.length > 0 ? (\n <InlineSelect\n options={selectOptions}\n value={selectValue}\n onChange={onSelectChange}\n disabled={disabled || selectDisabled}\n selectedOption={selectedOption}\n />\n ) : null);\n\n return (\n <div\n className={cn(\n \"relative flex flex-col gap-6 rounded-lg border border-border-primary bg-surface-primary\",\n \"has-focus-visible:outline-none\",\n \"motion-safe:transition-colors\",\n disabled && \"opacity-50\",\n className,\n )}\n >\n <div className=\"flex flex-col\">\n {hasAttachmentStrip ? (\n <div className=\"flex gap-2 overflow-x-auto px-4 pt-4 pb-2 [scrollbar-width:thin]\">\n {resolvedAttachmentStrip}\n </div>\n ) : null}\n <textarea\n {...textareaProps}\n ref={mergedRef}\n value={isControlled ? value : internalValue}\n placeholder={placeholder}\n maxLength={maxLength}\n disabled={disabled}\n aria-label={ariaLabel ?? placeholder}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n rows={minRows}\n className={cn(\n \"w-full resize-none bg-transparent px-4\",\n hasAttachmentStrip ? \"pt-0\" : \"pt-4\",\n \"typography-body-small-14px-regular text-content-primary\",\n \"placeholder:text-content-tertiary\",\n \"focus:outline-none disabled:cursor-not-allowed\",\n \"overflow-y-auto\",\n )}\n style={{\n minHeight: `${minHeight}px`,\n maxHeight: `${maxHeight}px`,\n ...style,\n }}\n />\n </div>\n\n <div className=\"flex items-center justify-between gap-2 px-4 pb-4\">\n <div className=\"flex items-center gap-1\">\n {showFileButton && (\n <IconButton\n variant=\"tertiary\"\n size=\"32\"\n icon={<AddIcon />}\n aria-label={fileButtonAriaLabel}\n onClick={onFileClick}\n disabled={disabled}\n className=\"sm:border sm:border-border-primary max-sm:-ml-2\"\n />\n )}\n </div>\n\n <div className=\"flex items-center gap-1\">\n {resolvedToolbarRight}\n <IconButton\n variant=\"primary\"\n size=\"32\"\n icon={submitIcon ?? <ArrowUpIcon />}\n aria-label={submitAriaLabel}\n onClick={handleSubmit}\n disabled={!canSubmit}\n className=\"disabled:bg-surface-secondary disabled:opacity-100 disabled:text-icons-primary\"\n />\n </div>\n </div>\n </div>\n );\n },\n);\n\nChatInput.displayName = \"ChatInput\";\n\ninterface InlineSelectProps {\n options: ChatInputSelectOption[];\n value?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n selectedOption?: ChatInputSelectOption;\n}\n\nfunction InlineSelect({ options, value, onChange, disabled, selectedOption }: InlineSelectProps) {\n const [open, setOpen] = React.useState(false);\n const containerRef = React.useRef<HTMLDivElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n const handleClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handleClick);\n return () => document.removeEventListener(\"mousedown\", handleClick);\n }, [open]);\n\n React.useEffect(() => {\n if (!open) return;\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", handleKey);\n return () => document.removeEventListener(\"keydown\", handleKey);\n }, [open]);\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n aria-label=\"Select model\"\n disabled={disabled}\n onClick={() => setOpen((prev) => !prev)}\n className={cn(\n \"typography-description-12px-semibold text-content-primary\",\n \"flex items-center gap-1 rounded-md px-2 py-2\",\n \"hover:bg-neutral-alphas-50 focus-visible:shadow-focus-ring focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"motion-safe:transition-colors\",\n )}\n >\n {selectedOption?.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{selectedOption.icon}</span>\n )}\n {selectedOption?.label ?? options[0]?.label ?? \"Select\"}\n <ChevronDownIcon\n className={cn(\"size-4 motion-safe:transition-transform\", open && \"rotate-180\")}\n />\n </button>\n\n {open && (\n <div\n role=\"listbox\"\n className={cn(\n \"absolute right-0 bottom-full z-10 mb-1 min-w-[140px]\",\n \"overflow-hidden rounded-xs border border-border-primary bg-surface-primary p-1 shadow-lg\",\n )}\n >\n {options.map((option) => (\n <div\n key={option.value}\n role=\"option\"\n tabIndex={0}\n aria-selected={option.value === value}\n className={cn(\n \"typography-body-small-14px-regular flex cursor-pointer items-center gap-2 rounded-xs px-3 py-1.5\",\n \"text-content-primary hover:bg-neutral-alphas-50\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n option.value === value && \"bg-neutral-alphas-50\",\n )}\n onClick={() => {\n onChange?.(option.value);\n setOpen(false);\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onChange?.(option.value);\n setOpen(false);\n }\n }}\n >\n {option.icon && (\n <span className=\"flex shrink-0 items-center [&>svg]:size-4\">{option.icon}</span>\n )}\n {option.label}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"names":["minHeight","maxHeight"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1092,6 +1092,8 @@ export declare interface ChatInputProps extends Omit<React_2.TextareaHTMLAttribu
1092
1092
  selectOptions?: ChatInputSelectOption[];
1093
1093
  /** Currently selected value for the built-in dropdown. Should match one of `selectOptions[].value`. */
1094
1094
  selectValue?: string;
1095
+ /** When `true`, disables only the built-in dropdown selector. @default false */
1096
+ selectDisabled?: boolean;
1095
1097
  /** Callback fired when the user picks a different dropdown option. */
1096
1098
  onSelectChange?: (value: string) => void;
1097
1099
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanvue/ui",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "React component library built with Tailwind CSS for Fanvue ecosystem",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",