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
This commit is contained in:
2025-10-06 19:30:35 -04:00
parent 2edf97bf1c
commit 99ade46247
3 changed files with 99 additions and 81 deletions

View File

@@ -51,21 +51,21 @@ const ServerSidebar: React.FC = () => {
return ( return (
<TooltipProvider> <TooltipProvider>
<div className="sidebar-primary flex flex-col items-center h-full py-2 space-y-2"> <div className="sidebar-primary flex flex-col items-center h-full space-y-2">
{/* Home/DM Button */} {/* Home/DM Button */}
<Tooltip> <Tooltip key={"home-server"}>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className={`w-12 h-12 ml-2 rounded-2xl hover:rounded-xl transition-all duration-200 ${ className={`w-10 h-10 p-0 mt-2 ml-2 rounded-full transition-all duration-200 ${
!instanceId || instanceId === "@me" !instanceId || instanceId === "@me"
? "bg-primary text-primary-foreground rounded-xl" ? "bg-primary text-primary-foreground"
: "hover:bg-primary/10" : "hover:bg-primary/10"
}`} }`}
onClick={handleHomeClick} onClick={handleHomeClick}
> >
<Home size={24} /> <Home size={4} />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="right"> <TooltipContent side="right">
@@ -74,7 +74,7 @@ const ServerSidebar: React.FC = () => {
</Tooltip> </Tooltip>
{/* Separator */} {/* Separator */}
<div className="w-12 ml-2 h-0.5 bg-border rounded-full" /> <div className="w-full ml-0 h-0.5 bg-border rounded-full" />
{/* Server List */} {/* Server List */}
<div className="flex-1 flex flex-col overflow-y-auto scrollbar-thin scrollbar-thumb-border space-y-2"> <div className="flex-1 flex flex-col overflow-y-auto scrollbar-thin scrollbar-thumb-border space-y-2">

View File

@@ -15,6 +15,7 @@ interface MessageInputProps {
replyingTo?: Message | null; replyingTo?: Message | null;
onCancelReply?: () => void; onCancelReply?: () => void;
replyingToUser: MessageUser | null; replyingToUser: MessageUser | null;
messageInputRef: React.RefObject<HTMLInputElement>;
} }
export const MessageInput: React.FC<MessageInputProps> = ({ export const MessageInput: React.FC<MessageInputProps> = ({
@@ -23,9 +24,10 @@ export const MessageInput: React.FC<MessageInputProps> = ({
replyingTo, replyingTo,
onCancelReply, onCancelReply,
replyingToUser, replyingToUser,
messageInputRef,
}) => { }) => {
const [content, setContent] = useState(""); const [content, setContent] = useState("");
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = messageInputRef;
const formRef = useRef<HTMLFormElement>(null); const formRef = useRef<HTMLFormElement>(null);
// Use the API hook for sending messages // Use the API hook for sending messages

View File

@@ -15,6 +15,7 @@ import { MessageInput } from "@/components/message/MessageInput";
const ChatPage: React.FC = () => { const ChatPage: React.FC = () => {
const { instanceId, channelId } = useParams(); const { instanceId, channelId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const messageInputRef = useRef<HTMLInputElement>(null);
const { const {
data: instance, data: instance,
@@ -39,6 +40,7 @@ const ChatPage: React.FC = () => {
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const messagesStartRef = useRef<HTMLDivElement>(null); const messagesStartRef = useRef<HTMLDivElement>(null);
const scrollAreaRef = useRef<HTMLDivElement>(null); // Ref for the ScrollArea content wrapper
// API mutation hooks - called unconditionally // API mutation hooks - called unconditionally
const loadMoreMessagesMutation = useLoadMoreMessages(channelId); const loadMoreMessagesMutation = useLoadMoreMessages(channelId);
@@ -71,6 +73,7 @@ const ChatPage: React.FC = () => {
// Effects - called unconditionally // Effects - called unconditionally
useEffect(() => { useEffect(() => {
// Scroll to bottom when messages load or update
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [channelMessages]); }, [channelMessages]);
@@ -102,6 +105,40 @@ const ChatPage: React.FC = () => {
[channelMessages], [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 // Handle loading states
if (instanceLoading || messagesLoading || usersLoading) { if (instanceLoading || messagesLoading || usersLoading) {
return ( return (
@@ -223,26 +260,8 @@ const ChatPage: React.FC = () => {
<div className="flex-1 flex flex-col overflow-y-auto"> <div className="flex-1 flex flex-col overflow-y-auto">
{/* Messages Area */} {/* Messages Area */}
<ScrollArea className="flex-1 min-h-0"> <ScrollArea className="flex-1 min-h-0">
{/* Load More Button */} {/* Attach ref to the actual scrollable content */}
{channelMessages && channelMessages.length > 0 && ( <div ref={scrollAreaRef} className="h-full overflow-y-auto">
<div className="flex justify-center py-2">
<Button
variant="ghost"
size="sm"
onClick={handleLoadMore}
disabled={isLoadingMore}
className="text-xs"
>
{isLoadingMore ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary mr-2"></div>
) : (
<Plus className="h-4 w-4 mr-2" />
)}
{isLoadingMore ? "Loading..." : "Load older messages"}
</Button>
</div>
)}
<div ref={messagesStartRef} /> <div ref={messagesStartRef} />
{/* Welcome Message */} {/* Welcome Message */}
@@ -257,11 +276,6 @@ const ChatPage: React.FC = () => {
</h3> </h3>
</div> </div>
</div> </div>
{currentChannel?.description && (
<div className="text-concord-secondary bg-concord-secondary/50 p-3 rounded border-l-4 border-primary">
{currentChannel.description}
</div>
)}
</div> </div>
<div className="pb-4"> <div className="pb-4">
@@ -302,6 +316,7 @@ const ChatPage: React.FC = () => {
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />
</div> </div>
</div>
</ScrollArea> </ScrollArea>
{/* Message Input */} {/* Message Input */}
@@ -317,6 +332,7 @@ const ChatPage: React.FC = () => {
? users?.find((u) => u.id === replyingTo.userId) || null ? users?.find((u) => u.id === replyingTo.userId) || null
: null : null
} }
messageInputRef={messageInputRef}
/> />
</div> </div>
)} )}