Skip to content

Commit a515789

Browse files
committed
feat: add NewMember and Imessage components with animations and messaging functionality
1 parent 23f528e commit a515789

File tree

5 files changed

+240
-16
lines changed

5 files changed

+240
-16
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { cn } from "@/lib/utils";
2+
import { motion as m } from "motion/react";
3+
import { Button } from "../ui/button";
4+
import { Input } from "../ui/input";
5+
6+
const members = [
7+
{
8+
name: "John Doe",
9+
email: "john.doe@example.com",
10+
image: "https://i.pravatar.cc/150?img=1",
11+
},
12+
{
13+
name: "Jane Doe",
14+
email: "jane.doe@example.com",
15+
image: "https://i.pravatar.cc/150?img=2",
16+
},
17+
{
18+
name: "John Doe",
19+
email: "john.doe@example.com",
20+
image: "https://i.pravatar.cc/150?img=1",
21+
},
22+
];
23+
24+
const NewMember = () => {
25+
return (
26+
<div className="full center">
27+
<div className="max-w-sm w-full flex flex-col gap-10">
28+
<div className="flex items-center justify-between gap-2">
29+
<Input placeholder="Enter your email" className="bg-muted" />
30+
<Button>Join waitlist</Button>
31+
</div>
32+
<div className="flex items-center justify-between">
33+
<div className="flex">
34+
{[...members, ...members].map((member, index) => {
35+
return (
36+
<m.img
37+
src={member.image}
38+
key={index}
39+
alt={member.name}
40+
className={cn(
41+
"w-10 h-10 rounded-full border-background border-2",
42+
index === 0 ? "" : "-mx-2"
43+
)}
44+
/>
45+
);
46+
})}
47+
</div>
48+
<div className="flex items-center gap-2 relative bg-muted rounded-full py-1 pr-4 pl-12 border">
49+
<div className="size-12 bg-background border p-0.5 min-w-12 rounded-full center overflow-hidden absolute -left-1 top-0 bottom-0 my-auto">
50+
<div className="size-full center bg-primary text-primary-foreground rounded-full">
51+
{180}
52+
</div>
53+
</div>
54+
<p className="text-sm">People joined today</p>
55+
</div>
56+
</div>
57+
</div>
58+
</div>
59+
);
60+
};
61+
62+
export default NewMember;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
"use client";
2+
3+
import { Mic, PlusIcon, Sticker } from "lucide-react";
4+
import { AnimatePresence, motion, Variants } from "motion/react";
5+
import { EditorContent, useEditor } from "@tiptap/react";
6+
import Starter from "@tiptap/starter-kit";
7+
import { useState } from "react";
8+
9+
const transitionDebug = {
10+
type: "easeOut",
11+
duration: 0.2,
12+
};
13+
14+
const Imessage = () => {
15+
const [messages, setMessages] = useState<
16+
{
17+
id: number;
18+
text: string;
19+
}[]
20+
>([]);
21+
const [newMessage, setNewMessage] = useState<string>("");
22+
23+
const handleSubmit = () => {
24+
if (newMessage.trim()) {
25+
const timestamp = new Date().getTime();
26+
setMessages([...messages, { id: timestamp, text: newMessage }]);
27+
setNewMessage("");
28+
}
29+
};
30+
31+
return (
32+
<div className="flex h-full flex-col items-end justify-end pb-6 mx-auto w-full gap-4">
33+
<div className="flex flex-col justify-end items-end gap-0.5 flex-1 h-full w-full overflow-hidden">
34+
<AnimatePresence mode="wait">
35+
{messages.map((message) => (
36+
<motion.div
37+
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}
42+
>
43+
<div className="px-3 py-2 text-base ">{message.text}</div>
44+
</motion.div>
45+
))}
46+
</AnimatePresence>
47+
</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" />
55+
</Button>
56+
</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>
74+
</div>
75+
);
76+
};
77+
78+
const Button = ({
79+
children,
80+
onClick,
81+
}: {
82+
children: React.ReactNode;
83+
onClick?: () => void;
84+
}) => {
85+
return (
86+
<button
87+
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}
90+
>
91+
{children}
92+
</button>
93+
);
94+
};
95+
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+
const Input = ({
102+
setMessage,
103+
handleSubmit,
104+
}: {
105+
setMessage: (message: string) => void;
106+
handleSubmit: () => void;
107+
}) => {
108+
const editor = useEditor({
109+
extensions: [
110+
Starter.configure({
111+
italic: false,
112+
}),
113+
],
114+
onUpdate: ({ editor }) => {
115+
setMessage(editor.getText());
116+
},
117+
});
118+
119+
const handleKeyDown = (event: React.KeyboardEvent) => {
120+
if (event.key === "Enter" && !event.shiftKey) {
121+
event.preventDefault();
122+
editor?.commands.clearContent();
123+
handleSubmit();
124+
}
125+
};
126+
return (
127+
<div className="flex flex-1 relative">
128+
<EditorContent
129+
editor={editor}
130+
className="max-h-96 overflow-y-auto min-h-10 outline-none py-2 w-full px-1"
131+
onKeyDown={handleKeyDown}
132+
starter-kit
133+
/>
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+
</div>
144+
);
145+
};
146+
147+
export default Imessage;

src/components/animations/index.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import NewMember from "@/components/animations/add-new-member";
12
import AmieAction from "@/components/animations/amie-action";
23
import NewHero from "@/components/animations/animate-text";
34
import Bolt from "@/components/animations/bolt";
@@ -7,54 +8,55 @@ import ClickSelect from "@/components/animations/click-select";
78
import ComponentPreview from "@/components/animations/component-preview";
89
import Counter from "@/components/animations/counter";
910
import Cursor from "@/components/animations/cursor";
11+
import CustomTextarea from "@/components/animations/custom-textarea";
12+
import DotMatrixClock from "@/components/animations/dotted-matric-clock";
1013
import DropdownNav from "@/components/animations/dropdown";
1114
import DynamicIsland from "@/components/animations/dynamic-island";
15+
import DynamicStatusButton from "@/components/animations/dynamic-status-button";
1216
import AnimatedFeedback from "@/components/animations/feedback";
1317
import Fellaz from "@/components/animations/fellaz";
1418
import FigmaLayout from "@/components/animations/figma-layout";
1519
import Uploader from "@/components/animations/file-uploader";
20+
import FluidButton from "@/components/animations/fluid-button";
1621
import AnimatedCounter from "@/components/animations/followers-count";
22+
import Imessage from "@/components/animations/imessage";
1723
import InputCheck from "@/components/animations/input-check";
1824
import InputShotcut from "@/components/animations/input-shotcut";
25+
import IslandMode from "@/components/animations/island-mode";
1926
import JoiDownloadButton from "@/components/animations/joi-download-button";
2027
import LinearTab from "@/components/animations/linear-tab";
2128
import LiveBlogs from "@/components/animations/live-blocks";
2229
import MagneticLines from "@/components/animations/magnetic-tiles";
30+
import ManagementBottomBar from "@/components/animations/management-bottom-bar";
31+
import Map from "@/components/animations/map";
32+
import ModeToggle from "@/components/animations/mode-toggle";
2333
import MovieGallery from "@/components/animations/movie-gallery";
2434
import MusicSheet from "@/components/animations/music-shit";
2535
import PeerListBar from "@/components/animations/peerlist-bar";
36+
import Plan from "@/components/animations/plan";
2637
import ProfileEdit from "@/components/animations/profile-edit";
2738
import { PromptBox } from "@/components/animations/prompt-box";
2839
import SearchUser from "@/components/animations/search-user";
2940
import SignIn from "@/components/animations/sign-in";
3041
import Slider from "@/components/animations/slider";
42+
import StackClick from "@/components/animations/stacked-click";
43+
import StatusButton from "@/components/animations/status-button";
3144
import TabBars from "@/components/animations/tab-bars";
45+
import Tasks from "@/components/animations/tasks";
3246
import TelegramInput from "@/components/animations/telegram-input";
47+
import TodoList from "@/components/animations/todo-list";
3348
import Typer from "@/components/animations/typer";
3449
import UserSearch from "@/components/animations/user-search";
3550
import UserStacked from "@/components/animations/user-stacked";
51+
import View from "@/components/animations/view";
52+
import Volume from "@/components/animations/volume";
3653
import Wants from "@/components/animations/wants";
3754
import Wheel from "@/components/animations/wheel";
3855
import WordRoll from "@/components/animations/word-roll";
3956
import YearsTabs from "@/components/animations/years-tabs";
4057
import Feedback from "@/components/shared/feedback";
4158
import Footer from "@/components/shared/footer";
4259
import Hero from "@/components/shared/hero";
43-
import CustomTextarea from "./custom-textarea";
44-
import DotMatrixClock from "./dotted-matric-clock";
45-
import DynamicStatusButton from "./dynamic-status-button";
46-
import FluidButton from "./fluid-button";
47-
import IslandMode from "./island-mode";
48-
import ManagementBottomBar from "./management-bottom-bar";
49-
import Map from "./map";
50-
import ModeToggle from "./mode-toggle";
51-
import Plan from "./plan";
52-
import StackClick from "./stacked-click";
53-
import StatusButton from "./status-button";
54-
import Tasks from "./tasks";
55-
import TodoList from "./todo-list";
56-
import View from "./view";
57-
import Volume from "./volume";
5860

5961
export {
6062
AmieAction,
@@ -78,6 +80,7 @@ export {
7880
Footer,
7981
Hero,
8082
ImageCarousel,
83+
Imessage,
8184
InputCheck,
8285
InputShotcut,
8386
IslandMode,
@@ -91,6 +94,7 @@ export {
9194
MovieGallery,
9295
MusicSheet,
9396
NewHero,
97+
NewMember,
9498
PeerListBar,
9599
Plan,
96100
ProfileEdit,

src/components/animations/telegram-input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ function Editor({ setInput, handleSubmit, messages }: EditorProps) {
200200
<div className="flex flex-col gap-4 flex-1 relative">
201201
<EditorContent
202202
editor={editor}
203-
className="max-h-96 overflow-y-auto py-3 outline-none rounded-lg min-h-full"
203+
className="max-h-96 overflow-y-auto py-3 outline-none rounded-lg min-h-full"
204204
onKeyDown={handleKeyDown}
205205
starter-kit
206206
/>

src/constants/components.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ type Component = {
99
};
1010

1111
export const COMPONENTS: Component[] = [
12+
{
13+
name: "iMessage",
14+
component: Animated.Imessage,
15+
href: "",
16+
},
17+
{
18+
name: "New Member",
19+
notReady: false,
20+
component: Animated.NewMember,
21+
href: "",
22+
},
1223
{
1324
name: "Task View",
1425
notReady: false,

0 commit comments

Comments
 (0)