154 lines
4.1 KiB
React
Raw Normal View History

2025-02-17 15:21:20 +07:00
"use client";;
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import { useTextareaResize } from "@/hooks/use-textarea-resize";
import { ArrowUpIcon } from "lucide-react";
import { createContext, useContext } from "react";
const ChatInputContext = createContext({});
function ChatInput({
children,
className,
variant = "default",
value,
onChange,
onSubmit,
loading,
onStop,
rows = 1
}) {
const contextValue = {
value,
onChange,
onSubmit,
loading,
onStop,
variant,
rows,
};
return (
(<ChatInputContext.Provider value={contextValue}>
<div
className={cn(variant === "default" &&
"flex flex-col items-end w-full p-2 rounded-2xl border border-input bg-transparent focus-within:ring-1 focus-within:ring-ring focus-within:outline-none", variant === "unstyled" && "flex items-start gap-2 w-full", className)}>
{children}
</div>
</ChatInputContext.Provider>)
);
}
ChatInput.displayName = "ChatInput";
function ChatInputTextArea({
onSubmit: onSubmitProp,
value: valueProp,
onChange: onChangeProp,
className,
variant: variantProp,
...props
}) {
const context = useContext(ChatInputContext);
const value = valueProp ?? context.value ?? "";
const onChange = onChangeProp ?? context.onChange;
const onSubmit = onSubmitProp ?? context.onSubmit;
const rows = context.rows ?? 1;
// Convert parent variant to textarea variant unless explicitly overridden
const variant =
variantProp ?? (context.variant === "default" ? "unstyled" : "default");
const textareaRef = useTextareaResize(value, rows);
const handleKeyDown = (e) => {
if (!onSubmit) {
return;
}
if (e.key === "Enter" && !e.shiftKey) {
if (typeof value !== "string" || value.trim().length === 0) {
return;
}
e.preventDefault();
onSubmit();
}
};
return (
(<Textarea
ref={textareaRef}
{...props}
value={value}
onChange={onChange}
onKeyDown={handleKeyDown}
className={cn(
"max-h-[400px] min-h-0 resize-none overflow-x-hidden",
variant === "unstyled" &&
"border-none focus-visible:ring-0 focus-visible:ring-offset-0 shadow-none",
className
)}
rows={rows} />)
);
}
ChatInputTextArea.displayName = "ChatInputTextArea";
function ChatInputSubmit({
onSubmit: onSubmitProp,
loading: loadingProp,
onStop: onStopProp,
className,
...props
}) {
const context = useContext(ChatInputContext);
const loading = loadingProp ?? context.loading;
const onStop = onStopProp ?? context.onStop;
const onSubmit = onSubmitProp ?? context.onSubmit;
if (loading && onStop) {
return (
(<Button
onClick={onStop}
className={cn("shrink-0 rounded-full p-1.5 h-fit border dark:border-zinc-600", className)}
{...props}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-label="Stop">
<title>Stop</title>
<rect x="6" y="6" width="12" height="12" />
</svg>
</Button>)
);
}
const isDisabled =
typeof context.value !== "string" || context.value.trim().length === 0;
return (
(<Button
className={cn("shrink-0 rounded-full p-1.5 h-fit border dark:border-zinc-600", className)}
disabled={isDisabled}
onClick={(event) => {
event.preventDefault();
if (!isDisabled) {
onSubmit?.();
}
}}
{...props}>
<ArrowUpIcon />
</Button>)
);
}
ChatInputSubmit.displayName = "ChatInputSubmit";
export { ChatInput, ChatInputTextArea, ChatInputSubmit };