From 99ade46247f8b7992e12e4dd0d319e29e9bfb8a6 Mon Sep 17 00:00:00 2001 From: Gabriel Garcia Date: Mon, 6 Oct 2025 19:30:35 -0400 Subject: [PATCH] ui: message improvements - Message input: improve focus - Server home icon: improve sizing and padding relative to other borders on page - Chat page: improve loading more messages through use effect scroll --- .../src/components/layout/ServerSidebar.tsx | 12 +- .../src/components/message/MessageInput.tsx | 4 +- concord-client/src/pages/ChatPage.tsx | 164 ++++++++++-------- 3 files changed, 99 insertions(+), 81 deletions(-) diff --git a/concord-client/src/components/layout/ServerSidebar.tsx b/concord-client/src/components/layout/ServerSidebar.tsx index 8308f80..a1b46e8 100644 --- a/concord-client/src/components/layout/ServerSidebar.tsx +++ b/concord-client/src/components/layout/ServerSidebar.tsx @@ -51,21 +51,21 @@ const ServerSidebar: React.FC = () => { return ( -
+
{/* Home/DM Button */} - + @@ -74,7 +74,7 @@ const ServerSidebar: React.FC = () => { {/* Separator */} -
+
{/* Server List */}
diff --git a/concord-client/src/components/message/MessageInput.tsx b/concord-client/src/components/message/MessageInput.tsx index 4708759..02a2660 100644 --- a/concord-client/src/components/message/MessageInput.tsx +++ b/concord-client/src/components/message/MessageInput.tsx @@ -15,6 +15,7 @@ interface MessageInputProps { replyingTo?: Message | null; onCancelReply?: () => void; replyingToUser: MessageUser | null; + messageInputRef: React.RefObject; } export const MessageInput: React.FC = ({ @@ -23,9 +24,10 @@ export const MessageInput: React.FC = ({ replyingTo, onCancelReply, replyingToUser, + messageInputRef, }) => { const [content, setContent] = useState(""); - const textareaRef = useRef(null); + const textareaRef = messageInputRef; const formRef = useRef(null); // Use the API hook for sending messages diff --git a/concord-client/src/pages/ChatPage.tsx b/concord-client/src/pages/ChatPage.tsx index 8d4c0f5..78bd66e 100644 --- a/concord-client/src/pages/ChatPage.tsx +++ b/concord-client/src/pages/ChatPage.tsx @@ -15,6 +15,7 @@ import { MessageInput } from "@/components/message/MessageInput"; const ChatPage: React.FC = () => { const { instanceId, channelId } = useParams(); const navigate = useNavigate(); + const messageInputRef = useRef(null); const { data: instance, @@ -39,6 +40,7 @@ const ChatPage: React.FC = () => { const messagesEndRef = useRef(null); const messagesStartRef = useRef(null); + const scrollAreaRef = useRef(null); // Ref for the ScrollArea content wrapper // API mutation hooks - called unconditionally const loadMoreMessagesMutation = useLoadMoreMessages(channelId); @@ -71,6 +73,7 @@ const ChatPage: React.FC = () => { // Effects - called unconditionally useEffect(() => { + // Scroll to bottom when messages load or update messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [channelMessages]); @@ -102,6 +105,40 @@ const ChatPage: React.FC = () => { [channelMessages], ); + // Focus the message input + useEffect(() => { + if (messageInputRef.current) { + messageInputRef.current.focus(); + } + }, [handleReply]); + + // Effect for scroll to top and load more + useEffect(() => { + const scrollAreaElement = scrollAreaRef.current; + + const handleScroll = () => { + if ( + scrollAreaElement && + scrollAreaElement.scrollTop === 0 && + channelMessages && + channelMessages.length > 0 && + !isLoadingMore + ) { + handleLoadMore(); + } + }; + + if (scrollAreaElement) { + scrollAreaElement.addEventListener("scroll", handleScroll); + } + + return () => { + if (scrollAreaElement) { + scrollAreaElement.removeEventListener("scroll", handleScroll); + } + }; + }, [channelMessages, isLoadingMore, handleLoadMore]); + // Handle loading states if (instanceLoading || messagesLoading || usersLoading) { return ( @@ -223,84 +260,62 @@ const ChatPage: React.FC = () => {
{/* Messages Area */} - {/* Load More Button */} - {channelMessages && channelMessages.length > 0 && ( -
- -
- )} + {/* Attach ref to the actual scrollable content */} +
+
-
- - {/* Welcome Message */} -
-
-
- -
-
-

- Welcome to #{currentChannel?.name}! -

-
-
- {currentChannel?.description && ( -
- {currentChannel.description} -
- )} -
- -
- {/* Messages */} - {sortedMessages && sortedMessages.length > 0 ? ( -
- {sortedMessages.map((message) => { - console.log(message); - const user = users?.find((u) => u.id === message.userId); - const replyToMessage = channelMessages?.find( - (m) => m.id === message.replies?.repliesToId, - ); - const replyToUser = replyToMessage - ? users?.find((u) => u.id === replyToMessage.userId) - : undefined; - - if (!user) return null; - - return ( - - ); - })} -
- ) : ( -
-
-

No messages yet. Start the conversation!

+ {/* Welcome Message */} +
+
+
+ +
+
+

+ Welcome to #{currentChannel?.name}! +

- )} +
-
+
+ {/* Messages */} + {sortedMessages && sortedMessages.length > 0 ? ( +
+ {sortedMessages.map((message) => { + console.log(message); + const user = users?.find((u) => u.id === message.userId); + const replyToMessage = channelMessages?.find( + (m) => m.id === message.replies?.repliesToId, + ); + const replyToUser = replyToMessage + ? users?.find((u) => u.id === replyToMessage.userId) + : undefined; + + if (!user) return null; + + return ( + + ); + })} +
+ ) : ( +
+
+

No messages yet. Start the conversation!

+
+
+ )} + +
+
@@ -317,6 +332,7 @@ const ChatPage: React.FC = () => { ? users?.find((u) => u.id === replyingTo.userId) || null : null } + messageInputRef={messageInputRef} />
)}