fix: wrap suggestion chips without overlapping input (#1895)

* fix: wrap suggestion chips without overlapping input

* fix: fix lint error
This commit is contained in:
Zhou 2026-04-06 16:30:57 +08:00 committed by GitHub
parent dd30e609f7
commit 55e78de6fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 46 additions and 42 deletions

View File

@ -1,10 +1,11 @@
"use client"; "use client";
import type { LucideIcon } from "lucide-react";
import { Children, type ComponentProps } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { LucideIcon } from "lucide-react";
import { Children, type ComponentProps } from "react";
const STAGGER_DELAY_MS = 60; const STAGGER_DELAY_MS = 60;
const STAGGER_DELAY_MS_OFFSET = 250; const STAGGER_DELAY_MS_OFFSET = 250;
@ -16,12 +17,15 @@ export const Suggestions = ({
children, children,
...props ...props
}: SuggestionsProps) => ( }: SuggestionsProps) => (
<ScrollArea className="overflow-x-auto whitespace-nowrap" {...props}> <ScrollArea className="overflow-x-auto whitespace-normal" {...props}>
<div className={cn("flex w-max flex-nowrap items-center gap-2", className)}> <div
className={cn("flex w-full flex-wrap items-center gap-2", className)}
data-slot="suggestions-list"
>
{Children.map(children, (child, index) => {Children.map(children, (child, index) =>
child != null ? ( child != null ? (
<span <span
className="animate-fade-in-up inline-block opacity-0" className="animate-fade-in-up max-w-full opacity-0"
style={{ style={{
animationDelay: `${STAGGER_DELAY_MS_OFFSET + index * STAGGER_DELAY_MS}ms`, animationDelay: `${STAGGER_DELAY_MS_OFFSET + index * STAGGER_DELAY_MS}ms`,
}} }}
@ -60,7 +64,7 @@ export const Suggestion = ({
return ( return (
<Button <Button
className={cn( className={cn(
"text-muted-foreground cursor-pointer rounded-full px-4 text-xs font-normal", "text-muted-foreground h-auto max-w-full cursor-pointer rounded-full px-4 py-2 text-center text-xs font-normal whitespace-normal",
className, className,
)} )}
onClick={handleClick} onClick={handleClick}
@ -70,7 +74,7 @@ export const Suggestion = ({
{...props} {...props}
> >
{Icon && <Icon className="size-4" />} {Icon && <Icon className="size-4" />}
{children || suggestion} {children ?? suggestion}
</Button> </Button>
); );
}; };

View File

@ -429,7 +429,38 @@ export function InputBox({
}, [context.model_name, disabled, isMock, status, thread.messages, threadId]); }, [context.model_name, disabled, isMock, status, thread.messages, threadId]);
return ( return (
<div ref={promptRootRef} className="relative"> <div ref={promptRootRef} className="relative flex flex-col gap-4">
{showFollowups && (
<div className="flex items-center justify-center pb-2">
<div className="flex items-center gap-2">
{followupsLoading ? (
<div className="text-muted-foreground bg-background/80 rounded-full border px-4 py-2 text-xs backdrop-blur-sm">
{t.inputBox.followupLoading}
</div>
) : (
<Suggestions className="min-h-16 w-fit items-start">
{followups.map((s) => (
<Suggestion
key={s}
suggestion={s}
onClick={() => handleFollowupClick(s)}
/>
))}
<Button
aria-label={t.common.close}
className="text-muted-foreground cursor-pointer rounded-full px-3 text-xs font-normal"
variant="outline"
size="sm"
type="button"
onClick={() => setFollowupsHidden(true)}
>
<XIcon className="size-4" />
</Button>
</Suggestions>
)}
</div>
</div>
)}
<PromptInput <PromptInput
className={cn( className={cn(
"bg-background/85 rounded-2xl backdrop-blur-sm transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-2xl", "bg-background/85 rounded-2xl backdrop-blur-sm transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-2xl",
@ -807,45 +838,14 @@ export function InputBox({
/> />
</PromptInputTools> </PromptInputTools>
</PromptInputFooter> </PromptInputFooter>
{isNewThread && searchParams.get("mode") !== "skill" && (
<div className="absolute right-0 -bottom-20 left-0 z-0 flex items-center justify-center">
<SuggestionList />
</div>
)}
{!isNewThread && ( {!isNewThread && (
<div className="bg-background absolute right-0 -bottom-[17px] left-0 z-0 h-4"></div> <div className="bg-background absolute right-0 -bottom-[17px] left-0 z-0 h-4"></div>
)} )}
</PromptInput> </PromptInput>
{showFollowups && ( {isNewThread && searchParams.get("mode") !== "skill" && (
<div className="absolute -top-20 right-0 left-0 z-20 flex items-center justify-center"> <div className="flex items-center justify-center pt-2">
<div className="flex items-center gap-2"> <SuggestionList />
{followupsLoading ? (
<div className="text-muted-foreground bg-background/80 rounded-full border px-4 py-2 text-xs backdrop-blur-sm">
{t.inputBox.followupLoading}
</div>
) : (
<Suggestions className="min-h-16 w-fit items-start">
{followups.map((s) => (
<Suggestion
key={s}
suggestion={s}
onClick={() => handleFollowupClick(s)}
/>
))}
<Button
aria-label={t.common.close}
className="text-muted-foreground cursor-pointer rounded-full px-3 text-xs font-normal"
variant="outline"
size="sm"
type="button"
onClick={() => setFollowupsHidden(true)}
>
<XIcon className="size-4" />
</Button>
</Suggestions>
)}
</div>
</div> </div>
)} )}