优化会话输入框出现滚动条时的样式

This commit is contained in:
xintaofei
2026-03-16 22:13:07 +08:00
parent 53911f1e78
commit 49130a6157

View File

@@ -1107,15 +1107,6 @@ export function MessageInput({
const hasImageAttachments = imageAttachments.length > 0 const hasImageAttachments = imageAttachments.length > 0
const hasResourceAttachments = resourceAttachments.length > 0 const hasResourceAttachments = resourceAttachments.length > 0
const topPaddingClass =
hasImageAttachments && hasResourceAttachments
? "pt-[6.25rem]"
: hasImageAttachments
? "pt-[4.5rem]"
: hasResourceAttachments
? "pt-10"
: "pt-3"
const bottomPaddingClass = "pb-10"
const showDragActive = isDragActive && !disabled const showDragActive = isDragActive && !disabled
const selectorItems = ( const selectorItems = (
@@ -1144,6 +1135,88 @@ export function MessageInput({
</> </>
) )
const actionButtons = isEditingQueueItem ? (
<div className="flex items-center gap-1">
<Button
onClick={onCancelQueueEdit}
variant="ghost"
size="icon"
className="h-8 w-8"
title={tQueue("cancelEdit")}
>
<X className="h-4 w-4" />
</Button>
<Button
onClick={handleSend}
disabled={!hasSendableContent}
size="icon"
title={tQueue("saveEdit")}
>
<Check className="h-4 w-4" />
</Button>
</div>
) : isPrompting && onCancel ? (
<div className="flex items-center gap-1">
<Button
onClick={handleSend}
disabled={!hasSendableContent}
variant="secondary"
size="icon"
className="h-8 w-8"
title={tQueue("addToQueue")}
>
<ListPlus className="h-4 w-4" />
</Button>
<Button
onClick={onCancel}
variant="destructive"
size="icon"
title={t("cancel")}
>
<Square className="h-4 w-4" />
</Button>
</div>
) : onForkSend ? (
<div className="flex items-center">
<Button
onClick={handleSend}
disabled={disabled || !hasSendableContent}
size="icon"
className="rounded-r-none"
title={t("send")}
>
<Send className="h-4 w-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
disabled={disabled || !hasSendableContent}
size="icon"
className="rounded-l-none border-l border-primary-foreground/20 w-6"
aria-label={t("forkAndSend")}
>
<ChevronUp className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="top">
<DropdownMenuItem onSelect={handleForkSendClick}>
<GitFork className="h-4 w-4" />
{t("forkAndSend")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
) : (
<Button
onClick={handleSend}
disabled={disabled || !hasSendableContent}
size="icon"
title={t("send")}
>
<Send className="h-4 w-4" />
</Button>
)
return ( return (
<div <div
ref={containerRef} ref={containerRef}
@@ -1159,204 +1232,125 @@ export function MessageInput({
onSelect={handleSlashSelect} onSelect={handleSlashSelect}
/> />
)} )}
<Textarea <div
ref={textareaRef}
value={text}
onChange={handleTextChange}
onKeyDown={handleKeyDown}
onCompositionStart={() => (composingRef.current = true)}
onCompositionEnd={() => (composingRef.current = false)}
onPaste={handlePaste}
onFocus={onFocus}
placeholder={resolvedPlaceholder}
className={cn( className={cn(
"text-sm pr-12 resize-none bg-transparent", "flex flex-col rounded-xl border border-input bg-transparent transition-colors focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50",
showDragActive && "ring-1 ring-primary/40", showDragActive && "ring-1 ring-primary/40",
topPaddingClass,
bottomPaddingClass,
className className
)} )}
autoFocus={autoFocus} >
/> {(hasImageAttachments || hasResourceAttachments) && (
{(hasImageAttachments || hasResourceAttachments) && ( <div className="flex shrink-0 flex-col gap-1 px-2 pt-2">
<div className="absolute left-2 right-12 top-2 z-10 flex flex-col gap-1"> {hasImageAttachments && (
{hasImageAttachments && ( <div className="flex items-center gap-1 overflow-x-auto pb-0.5">
<div className="flex items-center gap-1 overflow-x-auto pb-0.5"> {imageAttachments.map((attachment) => (
{imageAttachments.map((attachment) => ( <div
<div key={attachment.id}
key={attachment.id} className="relative shrink-0 overflow-hidden rounded-md border border-border/70 bg-muted/30"
className="relative shrink-0 overflow-hidden rounded-md border border-border/70 bg-muted/30"
>
<Image
src={`data:${attachment.mimeType};base64,${attachment.data}`}
alt={attachment.name}
width={56}
height={56}
unoptimized
className="h-14 w-14 object-cover"
/>
<button
type="button"
onClick={() => removeAttachment(attachment.id)}
className="absolute right-1 top-1 rounded-sm bg-background/70 p-0.5 hover:bg-background"
aria-label={t("removeAttachmentAria", {
name: attachment.name,
})}
> >
<X className="h-3 w-3" /> <Image
</button> src={`data:${attachment.mimeType};base64,${attachment.data}`}
</div> alt={attachment.name}
))} width={56}
</div> height={56}
)} unoptimized
{hasResourceAttachments && ( className="h-14 w-14 object-cover"
<div className="flex items-center gap-1 overflow-x-auto"> />
{resourceAttachments.map((attachment) => ( <button
<div type="button"
key={attachment.id} onClick={() => removeAttachment(attachment.id)}
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-border/70 bg-muted/40 px-2 text-[11px] text-muted-foreground" className="absolute right-1 top-1 rounded-sm bg-background/70 p-0.5 hover:bg-background"
> aria-label={t("removeAttachmentAria", {
<FileSearch className="h-3 w-3" /> name: attachment.name,
<span className="max-w-40 truncate">{attachment.name}</span> })}
<button >
type="button" <X className="h-3 w-3" />
onClick={() => removeAttachment(attachment.id)} </button>
className="rounded-sm p-0.5 hover:bg-muted-foreground/15" </div>
aria-label={t("removeAttachmentAria", { ))}
name: attachment.name, </div>
})} )}
{hasResourceAttachments && (
<div className="flex items-center gap-1 overflow-x-auto">
{resourceAttachments.map((attachment) => (
<div
key={attachment.id}
className="inline-flex h-6 shrink-0 items-center gap-1 rounded-full border border-border/70 bg-muted/40 px-2 text-[11px] text-muted-foreground"
> >
<X className="h-3 w-3" /> <FileSearch className="h-3 w-3" />
</button> <span className="max-w-40 truncate">{attachment.name}</span>
</div> <button
))} type="button"
</div> onClick={() => removeAttachment(attachment.id)}
)} className="rounded-sm p-0.5 hover:bg-muted-foreground/15"
aria-label={t("removeAttachmentAria", {
name: attachment.name,
})}
>
<X className="h-3 w-3" />
</button>
</div>
))}
</div>
)}
</div>
)}
<Textarea
ref={textareaRef}
value={text}
onChange={handleTextChange}
onKeyDown={handleKeyDown}
onCompositionStart={() => (composingRef.current = true)}
onCompositionEnd={() => (composingRef.current = false)}
onPaste={handlePaste}
onFocus={onFocus}
placeholder={resolvedPlaceholder}
className="min-h-0 flex-1 overflow-y-auto border-0 bg-transparent text-sm shadow-none focus-visible:border-0 focus-visible:ring-0"
autoFocus={autoFocus}
/>
<div className="@container flex shrink-0 items-end justify-between gap-2 px-2 pb-2">
<div className="flex min-w-0 items-end gap-1">
<Button
onClick={handlePickFiles}
disabled={disabled}
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0"
title={t("attachFiles")}
>
<Plus className="size-4" />
</Button>
{/* 宽屏内联显示,窄屏(<300px通过"更多"气泡显示 */}
<div className="hidden @[300px]:contents">{selectorItems}</div>
{hasAnySelector && (
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 @[300px]:hidden"
>
<Ellipsis className="size-4" />
</Button>
</PopoverTrigger>
<PopoverContent
side="top"
align="start"
className="flex w-auto flex-col gap-1 rounded-xl p-1"
>
{selectorItems}
</PopoverContent>
</Popover>
)}
</div>
<div className="shrink-0">{actionButtons}</div>
</div> </div>
)} </div>
{showDragActive && ( {showDragActive && (
<div className="pointer-events-none absolute inset-1 z-20 flex items-center justify-center rounded-md border border-dashed border-primary/50 bg-background/80 text-xs text-muted-foreground"> <div className="pointer-events-none absolute inset-1 z-20 flex items-center justify-center rounded-md border border-dashed border-primary/50 bg-background/80 text-xs text-muted-foreground">
{t("dropFilesToAttach")} {t("dropFilesToAttach")}
</div> </div>
)} )}
<div className="@container absolute left-2 right-24 bottom-2">
<div className="flex items-center gap-1">
<Button
onClick={handlePickFiles}
disabled={disabled}
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0"
title={t("attachFiles")}
>
<Plus className="size-4" />
</Button>
{/* 宽屏内联显示,窄屏(<300px通过"更多"气泡显示 */}
<div className="hidden @[300px]:contents">{selectorItems}</div>
{hasAnySelector && (
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 @[300px]:hidden"
>
<Ellipsis className="size-4" />
</Button>
</PopoverTrigger>
<PopoverContent
side="top"
align="start"
className="flex w-auto flex-col gap-1 rounded-xl p-1"
>
{selectorItems}
</PopoverContent>
</Popover>
)}
</div>
</div>
{isEditingQueueItem ? (
<div className="absolute right-2 bottom-2 flex items-center gap-1">
<Button
onClick={onCancelQueueEdit}
variant="ghost"
size="icon"
className="h-8 w-8"
title={tQueue("cancelEdit")}
>
<X className="h-4 w-4" />
</Button>
<Button
onClick={handleSend}
disabled={!hasSendableContent}
size="icon"
title={tQueue("saveEdit")}
>
<Check className="h-4 w-4" />
</Button>
</div>
) : isPrompting && onCancel ? (
<div className="absolute right-2 bottom-2 flex items-center gap-1">
<Button
onClick={handleSend}
disabled={!hasSendableContent}
variant="secondary"
size="icon"
className="h-8 w-8"
title={tQueue("addToQueue")}
>
<ListPlus className="h-4 w-4" />
</Button>
<Button
onClick={onCancel}
variant="destructive"
size="icon"
title={t("cancel")}
>
<Square className="h-4 w-4" />
</Button>
</div>
) : onForkSend ? (
<div className="absolute right-2 bottom-2 flex items-center">
<Button
onClick={handleSend}
disabled={disabled || !hasSendableContent}
size="icon"
className="rounded-r-none"
title={t("send")}
>
<Send className="h-4 w-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
disabled={disabled || !hasSendableContent}
size="icon"
className="rounded-l-none border-l border-primary-foreground/20 w-6"
aria-label={t("forkAndSend")}
>
<ChevronUp className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="top">
<DropdownMenuItem onSelect={handleForkSendClick}>
<GitFork className="h-4 w-4" />
{t("forkAndSend")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
) : (
<Button
onClick={handleSend}
disabled={disabled || !hasSendableContent}
size="icon"
className="absolute right-2 bottom-2"
title={t("send")}
>
<Send className="h-4 w-4" />
</Button>
)}
</div> </div>
) )
} }