diff --git a/frontend/app/element/chatmessages.less b/frontend/app/element/chatmessages.less new file mode 100644 index 000000000..7321fc764 --- /dev/null +++ b/frontend/app/element/chatmessages.less @@ -0,0 +1,51 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +.chat-messages { + height: 100%; + width: 100%; + padding: 10px; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.chat-message { + display: flex; + align-items: flex-start; + align-items: center; + margin-bottom: 8px; + font-size: 1em; + line-height: 1.4; +} + +.chat-user-icon { + height: 1em; /* Make user icon height match the text height */ + width: 1em; /* Keep the icon proportional */ + border-radius: 50%; + margin-right: 8px; +} + +.chat-username { + font-weight: bold; + margin-right: 4px; + line-height: 1.4; /* Ensure alignment with the first line of the message */ +} + +.chat-text { + display: flex; + align-items: flex-start; + flex-wrap: wrap; +} + +.chat-text img { + height: 1em; /* Make inline images (rendered via markdown) match the text height */ + width: auto; /* Keep the aspect ratio of images */ + margin: 0 4px; + display: inline; +} + +.chat-emoji { + margin: 0 2px; + font-size: 1em; /* Match emoji size with the text height */ +} diff --git a/frontend/app/element/chatmessages.stories.tsx b/frontend/app/element/chatmessages.stories.tsx new file mode 100644 index 000000000..1979c99e5 --- /dev/null +++ b/frontend/app/element/chatmessages.stories.tsx @@ -0,0 +1,90 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import type { Meta, StoryObj } from "@storybook/react"; +import { ChatMessages } from "./chatmessages"; +import "./chatmessages.less"; + +export interface ChatMessage { + id: string; + username: string; + message: string; + color?: string; + userIcon?: string; + messageIcon?: string; +} + +const meta = { + title: "Elements/ChatMessages", + component: ChatMessages, + args: { + messages: [ + { + id: "1", + username: "User1", + message: "Hello everyone! 👋", + color: "#ff4500", + userIcon: "https://via.placeholder.com/50", + }, + { + id: "2", + username: "User2", + message: "Check this out: ![cool icon](https://via.placeholder.com/20)", + color: "#1e90ff", + }, + { + id: "3", + username: "User3", + message: "This is a simple text message without icons.", + color: "#32cd32", + userIcon: "https://via.placeholder.com/50", + }, + { + id: "4", + username: "User4", + message: "🎉 👏 Great job!", + color: "#ff6347", + }, + { + id: "5", + username: "User5", + message: "Look at this cool icon: Isn't it awesome? ![cool icon](https://via.placeholder.com/20)", + color: "#8a2be2", + userIcon: "https://via.placeholder.com/50", + }, + ], + }, + argTypes: { + messages: { + description: "Array of chat messages to be displayed", + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Messages: Story = { + render: (args) => ( +
+ +
+ ), +}; + +export const ScrollableMessages: Story = { + render: (args) => ( +
+ +
+ ), + args: { + messages: Array.from({ length: 50 }, (_, i) => ({ + id: `${i + 1}`, + username: `User${i + 1}`, + message: `This is message number ${i + 1}.`, + color: i % 2 === 0 ? "#ff6347" : "#1e90ff", + userIcon: "https://via.placeholder.com/50", + })), + }, +}; diff --git a/frontend/app/element/chatmessages.tsx b/frontend/app/element/chatmessages.tsx new file mode 100644 index 000000000..a16316e3c --- /dev/null +++ b/frontend/app/element/chatmessages.tsx @@ -0,0 +1,60 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { Markdown } from "@/app/element/markdown"; +import clsx from "clsx"; +import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; +import { memo, useEffect, useRef } from "react"; + +import "./chatmessages.less"; + +export interface ChatMessage { + id: string; + username: string; + message: string; + color?: string; + userIcon?: string; +} + +interface ChatMessagesProps { + messages: ChatMessage[]; + className?: string; +} + +const ChatMessages = memo(({ messages, className }: ChatMessagesProps) => { + const messagesEndRef = useRef(null); + const overlayScrollRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + return ( + + {messages.map(({ id, username, message, color, userIcon }) => ( +
+ {userIcon && user icon} + + {username}: + + + + +
+ ))} +
+ + ); +}); + +ChatMessages.displayName = "ChatMessages"; + +export { ChatMessages };