unnamed-ui
输入控件

Sender Responsive

响应式 Sender 组件:输入框和按钮默认单行布局,当内容溢出时自动切换为多行布局。

0个数据源
"use client";

import { useState } from "react";
import { ResponsiveSender } from "@/components/composed/sender/responsive-sender";
import {
  SenderResponsiveButtonGroup,
  SenderResponsiveSendButton,
} from "@/components/wuhan/blocks/sender/sender-responsive-01";

export function SenderResponsiveDemo() {
  const [value, setValue] = useState("");
  const [overflowStatus, setOverflowStatus] = useState(false);

  const canSend = value.trim().length > 0;

  return (
    <ResponsiveSender
      value={value}
      onChange={setValue}
      placeholder="输入内容..."
      getCanSend={({ value: v }) => v.trim().length > 0}
      sendDisabled={!canSend}
      submitOnEnter
      onOverflowChange={setOverflowStatus}
      onSend={() => setValue("")}
      buttonGroupChildren={
        <SenderResponsiveButtonGroup isOverflow={overflowStatus}>
          <div className="pr-[var(--Gap-gap-sm)] pl-[var(--Gap-gap-sm)] gap-[var(--Gap-gap-sm)] rounded-[var(--radius-sm)] bg-[var(--Container-bg-neutral-light)]">
            <span className="font-size-1 text-[var(--Text-text-primary)]">
              0个数据源
            </span>
          </div>
          <SenderResponsiveSendButton type="submit" disabled={!canSend} />
        </SenderResponsiveButtonGroup>
      }
    />
  );
}

核心特性

  • 自动溢出检测:使用 ResizeObserver 监听内容宽度变化
  • 平滑过渡动画:CSS 实现流畅布局切换
  • 灵活控制forceSingleLine 强制单行,onOverflowChange 监听状态

安装

pnpm dlx shadcn@latest add http://localhost:3000/r/wuhan/responsive-sender.json

布局行为

单行模式 (默认)
═══════════════════════════════════════════════════
│ [Textarea:flex-1]  [附件]  [发送按钮]           │
│          ↑ 自适应宽度                      ↑ 最右边 │
═══════════════════════════════════════════════════

多行模式 (溢出时)
═══════════════════════════════════════════════════
│ [Textarea:w-full]                                 │
│ ─────────────────────────────────────────────    │
│                        [附件]  [发送按钮]         │  ← 右对齐
═══════════════════════════════════════════════════

Usage

基础用法

import { useState } from "react";
import { ResponsiveSender } from "@/registry/wuhan/composed/sender/responsive-sender";

export function Example() {
  const [value, setValue] = useState("");
  const [isOverflow, setIsOverflow] = useState(false);

  return (
    <ResponsiveSender
      value={value}
      onChange={setValue}
      onSend={() => console.log("send", value)}
      placeholder="输入你的需求..."
      onOverflowChange={setIsOverflow}
    />
  );
}

响应式原语(Blocks 层)

"use client";

import {
  SenderResponsiveContainer,
  SenderResponsiveTextarea,
  SenderResponsiveInputRow,
  SenderResponsiveButtonGroup,
  SenderResponsiveAttachmentButton,
  SenderResponsiveSendButton,
} from "@/components/wuhan/blocks/sender/sender-responsive-01";
import { Paperclip } from "lucide-react";
import { useState, useRef, useEffect } from "react";

export function SenderResponsiveDefault() {
  const [value, setValue] = useState("");
  const [isOverflow, setIsOverflow] = useState(false);
  const containerRef = useRef<HTMLFormElement>(null);

  // 检测溢出的简单示例
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { scrollWidth, clientWidth } = entry.target as HTMLElement;
        setIsOverflow(scrollWidth > clientWidth);
      }
    });

    resizeObserver.observe(container);
    return () => resizeObserver.disconnect();
  }, []);

  return (
    <SenderResponsiveContainer
      ref={containerRef}
      className="max-w-2xl"
      onOverflowChange={setIsOverflow}
    >
      <SenderResponsiveInputRow isOverflow={isOverflow}>
        <SenderResponsiveTextarea
          placeholder={isOverflow ? "输入内容..." : "输入消息..."}
          value={value}
          onChange={(e) => setValue(e.target.value)}
          isOverflow={isOverflow}
        />
        <SenderResponsiveButtonGroup isOverflow={isOverflow}>
          <SenderResponsiveAttachmentButton
            type="button"
            onClick={() => alert("点击附件")}
            aria-label="Attach file"
          >
            <Paperclip className="size-4" />
          </SenderResponsiveAttachmentButton>
          <SenderResponsiveSendButton
            type="submit"
            onClick={() => {
              if (value.trim()) {
                alert(`发送: ${value}`);
                setValue("");
              }
            }}
            disabled={!value.trim()}
          />
        </SenderResponsiveButtonGroup>
      </SenderResponsiveInputRow>
    </SenderResponsiveContainer>
  );
}
import {
  SenderResponsiveContainer,
  SenderResponsiveTextarea,
  SenderResponsiveInputRow,
  SenderResponsiveButtonGroup,
  SenderResponsiveAttachmentButton,
  SenderResponsiveSendButton,
} from "@/registry/wuhan/blocks/sender/sender-responsive-01";

export function Example() {
  const [value, setValue] = useState("");
  const [isOverflow, setIsOverflow] = useState(false);

  return (
    <SenderResponsiveContainer
      onOverflowChange={setIsOverflow}
    >
      <SenderResponsiveInputRow isOverflow={isOverflow}>
        <SenderResponsiveTextarea
          value={value}
          onChange={(e) => setValue(e.target.value)}
          isOverflow={isOverflow}
        />
        <SenderResponsiveButtonGroup isOverflow={isOverflow}>
          <SenderResponsiveAttachmentButton type="button">
            <Paperclip className="size-4" />
          </SenderResponsiveAttachmentButton>
          <SenderResponsiveSendButton
            type="submit"
            disabled={!value.trim()}
          />
        </SenderResponsiveButtonGroup>
      </SenderResponsiveInputRow>
    </SenderResponsiveContainer>
  );
}

API

ResponsiveSender (Composed)

布局控制

属性类型默认值说明
forceSingleLinebooleanfalse强制使用单行布局,禁用响应式切换
onOverflowChange(isOverflow) => void-溢出状态变化回调
maxWidthstring"100%"容器最大宽度

输入

属性类型默认值说明
valuestring-受控输入值
onChange(value) => void-输入变化回调
placeholderstring-占位符文字
inputDisabledbooleanfalse禁用输入框
submitOnEnterbooleanfalse按 Enter 发送

附件

属性类型默认值说明
attachmentsT[][]附件数组
attachmentAdapter(item) => AttachmentItem-数据适配器
onAttachmentRemove(id) => void-删除回调
maxAttachmentsnumber-最大附件数

发送

属性类型默认值说明
onSend() => void-发送回调
sendDisabledboolean-禁用发送
generatingbooleanfalse生成中状态
getCanSend(ctx) => boolean-自定义发送规则

Blocks 层组件

SenderResponsiveContainer

响应式容器,负责检测溢出状态。

属性类型默认值说明
maxWidthstring"100%"最大宽度阈值
forceSingleLinebooleanfalse强制单行模式
onOverflowChange(isOverflow) => void-溢出状态回调

SenderResponsiveInputRow

响应式输入行,始终保持 flex-row 布局。

属性类型默认值说明
isOverflowboolean-当前是否处于溢出状态

SenderResponsiveTextarea

响应式文本域,根据溢出状态调整宽度。

属性类型默认值说明
isOverflowboolean-当前是否处于溢出状态

SenderResponsiveButtonGroup

响应式按钮组,根据溢出状态切换对齐方式。

属性类型默认值说明
isOverflowboolean-当前是否处于溢出状态

SenderResponsiveAttachmentButton

响应式附件按钮。

SenderResponsiveSendButton

响应式发送按钮。

属性类型默认值说明
generatingbooleanfalse生成中状态
generatingContentReactNode-自定义加载内容