Skip to content

Commit 54c7c1e

Browse files
committed
feat: enhance iMessage component with improved animation transitions and message handling
1 parent f7e7838 commit 54c7c1e

File tree

1 file changed

+121
-63
lines changed

1 file changed

+121
-63
lines changed

src/components/animations/imessage.tsx

Lines changed: 121 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -6,156 +6,213 @@ import Placeholder from "@tiptap/extension-placeholder";
66
import Starter from "@tiptap/starter-kit";
77
import { Mic, PlusIcon, Sticker, X } from "lucide-react";
88
import { AnimatePresence, motion, Variants } from "motion/react";
9-
import { useState } from "react";
9+
import { useState, useRef } from "react";
1010

11-
const transitionDebug = {
12-
type: "easeOut",
13-
duration: 0.2,
11+
// Improved transition settings for smoother animations
12+
const smoothTransition = {
13+
type: "spring",
14+
stiffness: 500,
15+
damping: 40,
16+
mass: 1,
17+
};
18+
19+
// Animation variants
20+
const messageVariants: Variants = {
21+
initial: {
22+
opacity: 0,
23+
scale: 0.96,
24+
y: 20,
25+
},
26+
animate: {
27+
opacity: 1,
28+
scale: 1,
29+
y: 0,
30+
transition: {
31+
...smoothTransition,
32+
delay: 0.05,
33+
},
34+
},
35+
exit: {
36+
opacity: 0,
37+
scale: 0.96,
38+
transition: { duration: 0.2 },
39+
},
1440
};
1541

1642
const selectedMessageVariants: Variants = {
1743
initial: {
18-
y: 100,
44+
y: 5,
45+
opacity: 0,
1946
},
2047
animate: {
2148
y: 0,
49+
opacity: 1,
50+
transition: {
51+
...smoothTransition,
52+
},
53+
},
54+
exit: {
55+
y: -5,
56+
opacity: 0,
57+
transition: { duration: 0.2 },
2258
},
2359
};
60+
61+
const containerVariants: Variants = {
62+
collapsed: {
63+
borderRadius: "28px",
64+
},
65+
expanded: {
66+
borderRadius: "10px 10px 22px 22px",
67+
transition: smoothTransition,
68+
},
69+
};
70+
71+
interface Message {
72+
id: number;
73+
text: string;
74+
}
75+
2476
const Imessage = () => {
25-
const [messages, setMessages] = useState<
26-
{
27-
id: number;
28-
text: string;
29-
}[]
30-
>([]);
77+
const [messages, setMessages] = useState<Message[]>([]);
3178
const [selectedMessage, setSelectedMessage] = useState<string | null>(null);
79+
const [newMessage, setNewMessage] = useState("");
80+
const messagesEndRef = useRef<HTMLDivElement>(null);
3281

33-
const [newMessage, setNewMessage] = useState<string>("");
82+
const scrollToBottom = () => {
83+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
84+
};
3485

3586
const handleSubmit = () => {
3687
if (newMessage.trim()) {
3788
const timestamp = new Date().getTime();
3889
setMessages([...messages, { id: timestamp, text: newMessage }]);
3990
setNewMessage("");
91+
92+
// Scroll to bottom on new message
93+
setTimeout(scrollToBottom, 100);
4094
}
4195
};
4296

4397
return (
4498
<div className="flex h-full flex-col items-end justify-end pb-6 mx-auto w-full gap-4">
45-
<div className="flex flex-col justify-end items-end gap-0.5 flex-1 h-full w-full overflow-hidden">
46-
<AnimatePresence mode="wait">
99+
<div className="flex flex-col justify-end items-end gap-1 flex-1 h-full w-full overflow-y-auto overflow-x-hidden pr-1 pl-6">
100+
<AnimatePresence mode="popLayout">
47101
{messages.map((message) => (
48-
<div
102+
<motion.div
49103
key={message.id}
50104
className="flex flex-col items-end w-full"
51105
onDoubleClick={() => setSelectedMessage(message.text)}
106+
variants={messageVariants}
107+
initial="initial"
108+
animate="animate"
109+
exit="exit"
110+
layout
52111
>
53-
<motion.div
54-
layoutId={`container-[${messages.length - 1}]`}
55-
transition={transitionDebug}
56-
layout="position"
57-
className="px-3 py-2 text-base max-w-[80%] bg-background z-10 break-words rounded"
58-
>
112+
<div className="px-3 py-2 text-base max-w-[80%] bg-blue-500 text-white z-10 break-words rounded-2xl rounded-tr-sm">
59113
{message.text}
60-
</motion.div>
61-
</div>
114+
</div>
115+
</motion.div>
62116
))}
117+
<div ref={messagesEndRef} />
63118
</AnimatePresence>
64119
</div>
120+
65121
<motion.div
66122
className="w-full flex flex-col bg-background p-2 gap-2 relative overflow-hidden"
67-
animate={{
68-
borderRadius: selectedMessage ? "10px 10px 22px 22px" : "28px",
69-
}}
123+
variants={containerVariants}
124+
animate={selectedMessage ? "expanded" : "collapsed"}
125+
layout
70126
>
71127
<AnimatePresence>
72128
{selectedMessage && (
73129
<motion.div
74130
variants={selectedMessageVariants}
75131
initial="initial"
76132
animate="animate"
77-
exit="initial"
78-
className="w-full bg-muted h-20 rounded-md relative p-2 z-0"
133+
exit="exit"
134+
className="w-full bg-muted h-20 rounded-md relative p-2 z-10"
135+
layoutId="selectedMessage"
79136
>
80137
<p className="text-sm line-clamp-2 w-full text-muted-foreground">
81138
{selectedMessage}
82139
</p>
83140
<Button
84-
className="absolute top-0 right-0 size-6 center"
141+
className="absolute top-1 right-1 size-6 center"
85142
onClick={() => setSelectedMessage(null)}
86143
>
87144
<X className="size-4" />
88145
</Button>
89146
</motion.div>
90147
)}
91148
</AnimatePresence>
92-
<div className="flex w-full items-end z-[9999999] bg-background">
149+
150+
<motion.div
151+
layoutId="message-container-holder"
152+
className="flex w-full items-end z-20 bg-red-500 min-h-10 rounded-full "
153+
>
93154
<div className="flex gap-0.5">
94-
<Button>
155+
<Button className="">
95156
<PlusIcon className="size-5" />
96157
</Button>
97-
<Button>
158+
<Button className="">
98159
<Sticker className="size-5" />
99160
</Button>
100161
</div>
162+
101163
<Input setMessage={setNewMessage} handleSubmit={handleSubmit} />
102-
<motion.div
103-
key={messages.length}
104-
layout="position"
105-
className="pointer-events-none absolute z-10 flex h-9 w-[80%] items-center overflow-hidden break-words rounded-full [word-break:break-word] "
106-
layoutId={`container-[${messages.length}]`}
107-
transition={transitionDebug}
108-
initial={{ opacity: 0.6, zIndex: -1 }}
109-
animate={{ opacity: 0.6, zIndex: -1 }}
110-
exit={{ opacity: 1, zIndex: 1 }}
111-
>
112-
<div className="">{newMessage}</div>
113-
</motion.div>
114-
<Button className="size-10 group hover:bg-[#1daa61] rounded-full center transition-all duration-300">
115-
<Mic className="" />
164+
165+
<Button className="size-10 group hover:bg-[#1daa61] rounded-full flex items-center justify-center transition-all duration-300">
166+
<motion.div
167+
whileTap={{ scale: 0.92 }}
168+
transition={smoothTransition}
169+
>
170+
<Mic />
171+
</motion.div>
116172
</Button>
117-
</div>
173+
</motion.div>
118174
</motion.div>
119175
</div>
120176
);
121177
};
122178

123-
const Button = ({
124-
children,
125-
className,
126-
...props
127-
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
179+
interface ButtonProps {
128180
children: React.ReactNode;
129-
}) => {
181+
className?: string;
182+
onClick?: () => void;
183+
}
184+
185+
const Button: React.FC<ButtonProps> = ({ children, className, ...props }) => {
130186
return (
131-
<button
132-
type="submit"
187+
<motion.button
188+
type="button"
133189
className={cn(
134190
"flex size-10 items-center justify-center bg-background rounded-full hover:bg-muted transition-all duration-300",
135191
className
136192
)}
193+
whileHover={{ backgroundColor: "hsl(var(--muted))" }}
194+
whileTap={{ scale: 0.95 }}
195+
transition={smoothTransition}
137196
{...props}
138197
>
139198
{children}
140-
</button>
199+
</motion.button>
141200
);
142201
};
143202

144-
const Input = ({
145-
setMessage,
146-
handleSubmit,
147-
}: {
203+
interface InputProps {
148204
setMessage: (message: string) => void;
149205
handleSubmit: () => void;
150-
}) => {
206+
}
207+
208+
const Input: React.FC<InputProps> = ({ setMessage, handleSubmit }) => {
151209
const editor = useEditor({
152210
extensions: [
153211
Starter,
154212
Placeholder.configure({
155213
placeholder: "Type a message",
156214
}),
157215
],
158-
159216
onUpdate: ({ editor }) => {
160217
setMessage(editor.getText());
161218
},
@@ -168,11 +225,12 @@ const Input = ({
168225
handleSubmit();
169226
}
170227
};
228+
171229
return (
172230
<div className="flex flex-1 relative">
173231
<EditorContent
174232
editor={editor}
175-
className="max-h-96 overflow-y-auto min-h-10 outline-none py-2 w-full px-1 bg-background"
233+
className="max-h-96 overflow-y-auto min-h-10 outline-none py-2 w-full px-3 bg-background rounded-2xl"
176234
onKeyDown={handleKeyDown}
177235
/>
178236
</div>

0 commit comments

Comments
 (0)