Skip to content

Commit 03efe06

Browse files
add chat-input async behaviour
1 parent 97d6ecd commit 03efe06

File tree

1 file changed

+68
-37
lines changed

1 file changed

+68
-37
lines changed
Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,51 @@
11
import { useEffect, useRef, useState } from 'react';
2+
import { motion } from 'motion/react';
3+
import { PaperPlaneRightIcon, SpinnerIcon } from '@phosphor-icons/react/dist/ssr';
24
import { Button } from '@/components/livekit/button';
3-
import { cn } from '@/lib/utils';
5+
6+
const MOTION_PROPS = {
7+
variants: {
8+
hidden: {
9+
height: 0,
10+
opacity: 0,
11+
marginBottom: 0,
12+
},
13+
visible: {
14+
height: 'auto',
15+
opacity: 1,
16+
marginBottom: 12,
17+
},
18+
},
19+
initial: 'hidden',
20+
transition: {
21+
duration: 0.3,
22+
ease: 'easeOut',
23+
},
24+
};
425

526
interface ChatInputProps {
627
chatOpen: boolean;
728
disabled?: boolean;
829
onSend?: (message: string) => void;
930
}
1031

11-
export function ChatInput({ chatOpen, disabled = false, onSend = () => {} }: ChatInputProps) {
32+
export function ChatInput({ chatOpen, disabled = false, onSend = async () => {} }: ChatInputProps) {
1233
const inputRef = useRef<HTMLInputElement>(null);
34+
const [isSending, setIsSending] = useState(false);
1335
const [message, setMessage] = useState<string>('');
1436

15-
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
37+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
1638
e.preventDefault();
17-
onSend(message);
18-
setMessage('');
39+
40+
try {
41+
setIsSending(true);
42+
await onSend(message);
43+
setMessage('');
44+
} catch (error) {
45+
console.error(error);
46+
} finally {
47+
setIsSending(false);
48+
}
1949
};
2050

2151
const isDisabled = disabled || message.trim().length === 0;
@@ -27,40 +57,41 @@ export function ChatInput({ chatOpen, disabled = false, onSend = () => {} }: Cha
2757
}, [disabled]);
2858

2959
return (
30-
<div
60+
<motion.div
3161
inert={!chatOpen}
32-
className={cn(
33-
'overflow-hidden transition-[height] duration-300 ease-out',
34-
chatOpen ? 'h-[57px]' : 'h-0'
35-
)}
62+
{...MOTION_PROPS}
63+
animate={chatOpen ? 'visible' : 'hidden'}
64+
className="border-input/50 flex w-full items-start overflow-hidden border-b"
3665
>
37-
<div className="flex h-8 w-full">
38-
<form
39-
onSubmit={handleSubmit}
40-
className="flex grow items-center gap-2 rounded-md pl-1 text-sm"
66+
<form
67+
onSubmit={handleSubmit}
68+
className="mb-3 flex grow items-end gap-2 rounded-md pl-1 text-sm"
69+
>
70+
<input
71+
autoFocus
72+
ref={inputRef}
73+
type="text"
74+
value={message}
75+
disabled={disabled}
76+
placeholder="Type something..."
77+
onChange={(e) => setMessage(e.target.value)}
78+
className="h-8 flex-1 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
79+
/>
80+
<Button
81+
size="icon"
82+
type="submit"
83+
disabled={isDisabled}
84+
variant={isDisabled ? 'secondary' : 'primary'}
85+
title={isSending ? 'Sending...' : 'Send'}
86+
className="self-start"
4187
>
42-
<input
43-
autoFocus
44-
ref={inputRef}
45-
type="text"
46-
value={message}
47-
disabled={disabled}
48-
placeholder="Type something..."
49-
onChange={(e) => setMessage(e.target.value)}
50-
className="flex-1 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
51-
/>
52-
<Button
53-
size="sm"
54-
type="submit"
55-
disabled={isDisabled}
56-
variant={isDisabled ? 'secondary' : 'primary'}
57-
className="font-mono uppercase"
58-
>
59-
Send
60-
</Button>
61-
</form>
62-
</div>
63-
<hr className="border-input/50 my-3" />
64-
</div>
88+
{isSending ? (
89+
<SpinnerIcon className="animate-spin" weight="bold" />
90+
) : (
91+
<PaperPlaneRightIcon weight="bold" />
92+
)}
93+
</Button>
94+
</form>
95+
</motion.div>
6596
);
6697
}

0 commit comments

Comments
 (0)