1
1
"use client" ;
2
2
3
- import { Mic , PlusIcon , Sticker } from "lucide-react" ;
4
- import { AnimatePresence , motion , Variants } from "motion/react" ;
3
+ import { cn } from "@/lib/utils" ;
5
4
import { EditorContent , useEditor } from "@tiptap/react" ;
5
+ import Placeholder from "@tiptap/extension-placeholder" ;
6
6
import Starter from "@tiptap/starter-kit" ;
7
+ import { Mic , PlusIcon , Sticker , X } from "lucide-react" ;
8
+ import { AnimatePresence , motion , Variants } from "motion/react" ;
7
9
import { useState } from "react" ;
8
10
9
11
const transitionDebug = {
10
12
type : "easeOut" ,
11
13
duration : 0.2 ,
12
14
} ;
13
15
16
+ const selectedMessageVariants : Variants = {
17
+ initial : {
18
+ y : 50 ,
19
+ } ,
20
+ animate : {
21
+ y : 0 ,
22
+ } ,
23
+ } ;
14
24
const Imessage = ( ) => {
15
25
const [ messages , setMessages ] = useState <
16
26
{
17
27
id : number ;
18
28
text : string ;
19
29
} [ ]
20
30
> ( [ ] ) ;
31
+ const [ selectedMessage , setSelectedMessage ] = useState < string | null > ( null ) ;
32
+
21
33
const [ newMessage , setNewMessage ] = useState < string > ( "" ) ;
22
34
23
35
const handleSubmit = ( ) => {
@@ -33,71 +45,102 @@ const Imessage = () => {
33
45
< div className = "flex flex-col justify-end items-end gap-0.5 flex-1 h-full w-full overflow-hidden" >
34
46
< AnimatePresence mode = "wait" >
35
47
{ messages . map ( ( message ) => (
36
- < motion . div
48
+ < div
37
49
key = { message . id }
38
- layout = "position"
39
- className = "z-10 max-w-[80%] break-words rounded bg-background"
40
- layoutId = { `container-[${ messages . length - 1 } ]` }
41
- transition = { transitionDebug }
50
+ className = "flex flex-col items-end w-full"
51
+ onDoubleClick = { ( ) => setSelectedMessage ( message . text ) }
42
52
>
43
- < div className = "px-3 py-2 text-base " > { message . text } </ div >
44
- </ motion . div >
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
+ >
59
+ { message . text }
60
+ </ motion . div >
61
+ </ div >
45
62
) ) }
46
63
</ AnimatePresence >
47
64
</ div >
48
- < div className = "flex w-full items-end bg-background rounded-[22px] p-1" >
49
- < div className = "flex gap-0.5" >
50
- < Button >
51
- < PlusIcon className = "size-5" />
52
- </ Button >
53
- < Button >
54
- < Sticker className = "size-5" />
65
+ < motion . div
66
+ 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
+ } }
70
+ >
71
+ < AnimatePresence >
72
+ { selectedMessage && (
73
+ < motion . div
74
+ variants = { selectedMessageVariants }
75
+ initial = "initial"
76
+ animate = "animate"
77
+ exit = "initial"
78
+ className = "w-full bg-muted h-20 rounded-md relative p-2 z-0"
79
+ >
80
+ < p className = "text-sm line-clamp-2 w-full text-muted-foreground" >
81
+ { selectedMessage }
82
+ </ p >
83
+ < Button
84
+ className = "absolute top-0 right-0 size-6 center"
85
+ onClick = { ( ) => setSelectedMessage ( null ) }
86
+ >
87
+ < X className = "size-4" />
88
+ </ Button >
89
+ </ motion . div >
90
+ ) }
91
+ </ AnimatePresence >
92
+ < div className = "flex w-full items-end z-[9999999] bg-background" >
93
+ < div className = "flex gap-0.5" >
94
+ < Button >
95
+ < PlusIcon className = "size-5" />
96
+ </ Button >
97
+ < Button >
98
+ < Sticker className = "size-5" />
99
+ </ Button >
100
+ </ div >
101
+ < 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 = "" />
55
116
</ Button >
56
117
</ div >
57
- < Input setMessage = { setNewMessage } handleSubmit = { handleSubmit } />
58
- < motion . div
59
- key = { messages . length }
60
- layout = "position"
61
- className = "pointer-events-none absolute z-10 flex h-9 w-[80%] items-center overflow-hidden break-words rounded-full [word-break:break-word] "
62
- layoutId = { `container-[${ messages . length } ]` }
63
- transition = { transitionDebug }
64
- initial = { { opacity : 0.6 , zIndex : - 1 } }
65
- animate = { { opacity : 0.6 , zIndex : - 1 } }
66
- exit = { { opacity : 1 , zIndex : 1 } }
67
- >
68
- < div className = "" > { newMessage } </ div >
69
- </ motion . div >
70
- < Button >
71
- < Mic className = "size-5" />
72
- </ Button >
73
- </ div >
118
+ </ motion . div >
74
119
</ div >
75
120
) ;
76
121
} ;
77
122
78
123
const Button = ( {
79
124
children,
80
- onClick,
81
- } : {
125
+ className,
126
+ ...props
127
+ } : React . ButtonHTMLAttributes < HTMLButtonElement > & {
82
128
children : React . ReactNode ;
83
- onClick ?: ( ) => void ;
84
129
} ) => {
85
130
return (
86
131
< button
87
132
type = "submit"
88
- className = "flex size-10 items-center justify-center bg-background rounded-full hover:bg-muted transition-all duration-300"
89
- onClick = { onClick }
133
+ className = { cn (
134
+ "flex size-10 items-center justify-center bg-background rounded-full hover:bg-muted transition-all duration-300" ,
135
+ className
136
+ ) }
137
+ { ...props }
90
138
>
91
139
{ children }
92
140
</ button >
93
141
) ;
94
142
} ;
95
143
96
- const paragraphVariants : Variants = {
97
- hidden : { opacity : 0 , x : 10 , filter : "blur(10px)" } ,
98
- visible : { opacity : 1 , x : 0 , filter : "blur(0px)" } ,
99
- } ;
100
-
101
144
const Input = ( {
102
145
setMessage,
103
146
handleSubmit,
@@ -107,10 +150,12 @@ const Input = ({
107
150
} ) => {
108
151
const editor = useEditor ( {
109
152
extensions : [
110
- Starter . configure ( {
111
- italic : false ,
153
+ Starter ,
154
+ Placeholder . configure ( {
155
+ placeholder : "Type a message" ,
112
156
} ) ,
113
157
] ,
158
+
114
159
onUpdate : ( { editor } ) => {
115
160
setMessage ( editor . getText ( ) ) ;
116
161
} ,
@@ -127,19 +172,9 @@ const Input = ({
127
172
< div className = "flex flex-1 relative" >
128
173
< EditorContent
129
174
editor = { editor }
130
- className = "max-h-96 overflow-y-auto min-h-10 outline-none py-2 w-full px-1"
175
+ className = "max-h-96 overflow-y-auto min-h-10 outline-none py-2 w-full px-1 bg-background "
131
176
onKeyDown = { handleKeyDown }
132
- starter-kit
133
177
/>
134
- < motion . p
135
- variants = { paragraphVariants }
136
- animate = { editor ?. getText ( ) . length === 0 ? "visible" : "hidden" }
137
- exit = "hidden"
138
- initial = "hidden"
139
- className = "absolute left-1 top-0 bottom-0 my-auto h-fit text-neutral-500"
140
- >
141
- Type a message
142
- </ motion . p >
143
178
</ div >
144
179
) ;
145
180
} ;
0 commit comments