Please use Desktop to view and interact with components
Components300msspring
Chat App
Fully functional chat interface with animated messages
chatmessagesmessaginguishadcn
Useto navigate between components
Preview
Chat Support
Online
Hello! How can I help you today?
1:50 AMHi there! I was wondering about your features.
1:50 AMOf course! What would you like to know?
1:50 AMThis component requires shadcn/ui
This component uses shadcn/ui components. Make sure you have shadcn/ui set up in your project.
- Install shadcn/ui:
npx shadcn-ui@latest init - Install required components based on the imports in the code (e.g.,
npx shadcn-ui@latest add button) - Ensure your
tailwind.config.tsandglobals.cssare configured as per shadcn/ui documentation
Code
TypeScript + React
'use client'
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Send, User, Bot } from 'lucide-react'
type Message = {
id: number
text: string
sender: 'user' | 'bot'
timestamp: Date
}
const initialMessages: Message[] = [
{
id: 1,
text: 'Hello! How can I help you today?',
sender: 'bot',
timestamp: new Date(Date.now() - 60000),
},
{
id: 2,
text: 'Hi there! I was wondering about your features.',
sender: 'user',
timestamp: new Date(Date.now() - 30000),
},
{
id: 3,
text: 'Of course! What would you like to know?',
sender: 'bot',
timestamp: new Date(Date.now() - 15000),
},
]
export function ChatApp() {
const [messages, setMessages] = useState<Message[]>(initialMessages)
const [inputValue, setInputValue] = useState('')
const formatTime = (date: Date): string => {
const hours = date.getHours()
const minutes = date.getMinutes()
const ampm = hours >= 12 ? 'PM' : 'AM'
const displayHours = hours % 12 || 12
const displayMinutes = minutes.toString().padStart(2, '0')
return `${displayHours}:${displayMinutes} ${ampm}`
}
const handleSend = () => {
if (!inputValue.trim()) return
const newMessage: Message = {
id: messages.length + 1,
text: inputValue,
sender: 'user',
timestamp: new Date(),
}
setMessages((prev) => [...prev, newMessage])
setInputValue('')
// Simulate bot response
setTimeout(() => {
const botResponse: Message = {
id: messages.length + 2,
text: 'Thanks for your message! This is an automated response.',
sender: 'bot',
timestamp: new Date(),
}
setMessages((prev) => [...prev, botResponse])
}, 1000)
}
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSend()
}
}
return (
<Card className="flex h-[600px] w-full max-w-md flex-col overflow-hidden">
{/* Header */}
<div className="border-b border-border bg-muted/50 px-4 py-3">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary">
<Bot className="h-5 w-5 text-primary-foreground" />
</div>
<div>
<h3 className="font-semibold text-sm">Chat Support</h3>
<p className="text-xs text-muted-foreground">Online</p>
</div>
</div>
</div>
{/* Messages */}
<ScrollArea className="flex-1 px-4">
<div className="space-y-4 py-4">
<AnimatePresence mode="popLayout">
{messages.map((message, index) => (
<motion.div
key={message.id}
initial={{ opacity: 0, y: 20, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{
duration: 0.3,
delay: index === messages.length - 1 ? 0 : 0,
}}
className={`flex gap-3 ${
message.sender === 'user' ? 'flex-row-reverse' : 'flex-row'
}`}
>
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{
delay: 0.2,
type: 'spring',
stiffness: 200,
}}
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${
message.sender === 'user'
? 'bg-primary'
: 'bg-muted'
}`}
>
{message.sender === 'user' ? (
<User className="h-4 w-4 text-primary-foreground" />
) : (
<Bot className="h-4 w-4 text-muted-foreground" />
)}
</motion.div>
<div
className={`flex max-w-[80%] flex-col ${
message.sender === 'user' ? 'items-end' : 'items-start'
}`}
>
<motion.div
initial={{ scale: 0.9 }}
animate={{ scale: 1 }}
transition={{ delay: 0.1 }}
className={`rounded-lg px-4 py-2 text-sm ${
message.sender === 'user'
? 'bg-primary text-primary-foreground'
: 'bg-muted text-foreground'
}`}
>
{message.text}
</motion.div>
<span className="mt-1 text-xs text-muted-foreground">
{formatTime(message.timestamp)}
</span>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
</ScrollArea>
{/* Input */}
<div className="border-t border-border bg-background p-4">
<div className="flex gap-2">
<Input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Type a message..."
className="flex-1"
/>
<Button
onClick={handleSend}
size="icon"
disabled={!inputValue.trim()}
>
<Send className="h-4 w-4" />
</Button>
</div>
</div>
</Card>
)
}
How to Use
- 1Install Framer Motion:
npm install framer-motion - 2Set up shadcn/ui: Install shadcn/ui components used in this code. Check the imports in the code above and install the required components (e.g.,
npx shadcn-ui@latest add button card) - 3Copy the code from above
- 4Paste it into your project and customize as needed
- 5Colors are customizable via Tailwind CSS classes. The default theme uses dark mode colors defined in your globals.css file