From 40a4e54e9b5f4a0eda20280d0bb8779b960f2223 Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Mon, 15 Jun 2026 23:36:45 +0530 Subject: [PATCH 1/5] feat: transparent native matrix federation via joinRoom hook in EmbeddedChatApi --- packages/api/src/EmbeddedChatApi.ts | 19 ++++++++++++--- .../stories/EmbeddedChatWithMatrix.stories.js | 23 +++++++++++++++++++ .../react/src/views/ChatHeader/ChatHeader.js | 3 +-- packages/react/src/views/EmbeddedChat.js | 5 ++-- 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 packages/react/src/stories/EmbeddedChatWithMatrix.stories.js diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index b775016170..6f2408414e 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -238,12 +238,12 @@ export default class EmbeddedChatApi { * All subscriptions are implemented here. * TODO: Add logic to call thread message event listeners. To be done after thread implementation */ - async connect() { + async connect(isChannelPrivate = false) { // Guard against concurrent connect() calls (e.g. React StrictMode double-invoke) if (this._connectPromise) { return this._connectPromise; } - this._connectPromise = this._doConnect().finally(() => { + this._connectPromise = this._doConnect(isChannelPrivate).finally(() => { this._connectPromise = null; }); return this._connectPromise; @@ -261,7 +261,18 @@ export default class EmbeddedChatApi { return message; } - private async _doConnect() { + async joinRoom(isChannelPrivate = false) { + try { + const roomType = isChannelPrivate ? "groups" : "channels"; + await this._restRequest(`/v1/${roomType}.join`, "POST", { + roomId: this.rid, + }); + } catch (err) { + console.debug("[EmbeddedChat] joinRoom skipped:", err); + } + } + + private async _doConnect(isChannelPrivate = false) { try { this.close(); // before connection, all previous subscriptions should be cancelled await this.sdk.connection.connect(); @@ -269,7 +280,9 @@ export default class EmbeddedChatApi { const currentUser = (await this.auth.getCurrentUser()) as any; const token = currentUser?.authToken || currentUser?.data?.authToken; if (token) { + this._applyCredentials(currentUser); await this.sdk.account.loginWithToken(token); + await this.joinRoom(isChannelPrivate); } // Subscribe to room messages diff --git a/packages/react/src/stories/EmbeddedChatWithMatrix.stories.js b/packages/react/src/stories/EmbeddedChatWithMatrix.stories.js new file mode 100644 index 0000000000..cd5ceab5d6 --- /dev/null +++ b/packages/react/src/stories/EmbeddedChatWithMatrix.stories.js @@ -0,0 +1,23 @@ +import { EmbeddedChat } from '..'; + +export default { + title: 'EmbeddedChat/WithMatrix', + component: EmbeddedChat, +}; + +export const WithMatrix = { + args: { + host: process.env.STORYBOOK_RC_HOST || 'http://localhost:3000', + roomId: process.env.RC_ROOM_ID || 'GENERAL', + channelName: 'general', + anonymousMode: false, + toastBarPosition: 'bottom right', + showRoles: true, + enableThreads: true, + hideHeader: false, + auth: { + flow: 'PASSWORD', + }, + dark: false, + }, +}; diff --git a/packages/react/src/views/ChatHeader/ChatHeader.js b/packages/react/src/views/ChatHeader/ChatHeader.js index c28e9ad39e..0fcc7a7a5a 100644 --- a/packages/react/src/views/ChatHeader/ChatHeader.js +++ b/packages/react/src/views/ChatHeader/ChatHeader.js @@ -203,8 +203,7 @@ const ChatHeader = ({ ) { setIsChannelArchived(true); const roomInfo = await RCInstance.getRoomInfo(); - const roomData = roomInfo.result[roomInfo.result.length - 1]; - setChannelInfo(roomData); + setChannelInfo(roomInfo?.room); } else if ('errorType' in res && res.errorType === 'Not Allowed') { dispatchToastMessage({ type: 'error', diff --git a/packages/react/src/views/EmbeddedChat.js b/packages/react/src/views/EmbeddedChat.js index a15cdbccd1..153670ecef 100644 --- a/packages/react/src/views/EmbeddedChat.js +++ b/packages/react/src/views/EmbeddedChat.js @@ -18,7 +18,7 @@ import { import { ChatLayout } from './ChatLayout'; import { ChatHeader } from './ChatHeader'; import { RCInstanceProvider } from '../context/RCInstance'; -import { useUserStore, useLoginStore, useMessageStore } from '../store'; +import { useUserStore, useLoginStore, useMessageStore, useChannelStore } from '../store'; import DefaultTheme from '../theme/DefaultTheme'; import { getTokenStorage } from '../lib/auth'; import { styles } from './EmbeddedChat.styles'; @@ -172,7 +172,8 @@ const EmbeddedChat = (props) => { useEffect(() => { const handleAuthChange = (user) => { if (user) { - RCInstance.connect() + const { isChannelPrivate } = useChannelStore.getState(); + RCInstance.connect(isChannelPrivate) .then(() => { console.log(`Connected to RocketChat ${RCInstance.host}`); const me = user.me || user.data?.me; From 0b40425e22e4e7e311e5b2d52579605d14e8a00a Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Sun, 12 Jul 2026 20:21:41 +0530 Subject: [PATCH 2/5] feat: Matrix integration theme, timeline layout, and badges --- packages/docs/docs/Usage/matrix.md | 95 ++++++++++++++++ packages/docs/sidebars.js | 1 + .../stories/EmbeddedChatWithMatrix.stories.js | 4 +- packages/react/src/theme/MatrixTheme.js | 104 ++++++++++++++++++ .../react/src/views/ChatHeader/ChatHeader.js | 48 ++++++-- packages/react/src/views/EmbeddedChat.js | 15 ++- packages/react/src/views/Message/Message.js | 5 +- .../react/src/views/Message/Message.styles.js | 22 +++- .../react/src/views/Message/MessageHeader.js | 23 ++++ 9 files changed, 300 insertions(+), 17 deletions(-) create mode 100644 packages/docs/docs/Usage/matrix.md create mode 100644 packages/react/src/theme/MatrixTheme.js diff --git a/packages/docs/docs/Usage/matrix.md b/packages/docs/docs/Usage/matrix.md new file mode 100644 index 0000000000..8a4229ebbc --- /dev/null +++ b/packages/docs/docs/Usage/matrix.md @@ -0,0 +1,95 @@ +--- +title: Matrix Integration +--- + +# Matrix Federation Integration + +EmbeddedChat features native support for **Matrix Federation**, enabling seamless real-time communication with users on external Matrix servers (such as `matrix.org`). + +Through this integration, your EmbeddedChat instance behaves like a modern Matrix client: displaying Matrix-styled user interfaces, handling federated message styling, and routing room join handshakes through Rocket.Chat's native Matrix gateway. + +![EmbeddedChat Matrix Demo](https://github.com/user-attachments/assets/d1ec8072-4618-4af6-b13f-ef3046502dd1) + +--- + +## Key Features + +- **Matrix Theme (`theme="matrix"`)**: An Element-inspired styling scheme featuring emerald green brand colors (`#0dbd8b`) and a sleek charcoal dark mode (`#15191e`). +- **Timeline Layout Mode (`layoutMode="timeline"`)**: A flat, bubble-less, consecutive message layout reminiscent of classic chat and modern Matrix/Element clients. +- **Federated Status Badge**: A header indicator detailing the federated status of the channel and its origin homeserver. +- **Server Origin Badges**: Visual badges on message headers identifying external homeservers (e.g., `matrix.org`, `envs.net`) for federated users. +- **Proxy-based Federation Join**: Intelligent routing for room-join requests, automatically detecting federated room metadata and invoking the Matrix federation join API endpoint. + +--- + +## Configuration & Usage + +To configure the Matrix visual style and enable federation styling, pass the following props to the `EmbeddedChat` component: + +```jsx +import { EmbeddedChat } from '@embeddedchat/react'; + +function App() { + return ( + + ); +} +``` + +### Prop Details + +| Prop | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `theme` | `string` | `null` | Pass `"matrix"` to load the Element-inspired theme. | +| `dark` | `boolean` | `false` | Enable dark mode. When paired with `theme="matrix"`, this renders the charcoal dark aesthetic (`#15191e`). | +| `layoutMode` | `"default" \| "timeline"` | `"default"` | Set to `"timeline"` to enable the flat, compact message layout with a left hover border accent. | + +--- + +## Architectural Details + +### 1. Matrix Theme (`MatrixTheme.js`) +The Matrix Theme configures a tailored HSL color palette mapping exactly to Element Web's color tokens: +- **Brand Green**: `hsl(163, 88%, 40%)` (`#0dbd8b`) +- **Dark Background**: `hsl(216, 17%, 10%)` (`#15191e`) +- **Message Cards / Popovers**: `hsl(220, 12%, 20%)` (`#2c3038`) +- **Border / Outline Lines**: `hsl(215, 11%, 25%)` (`#394049`) + +### 2. Timeline Layout Mode +When `layoutMode` is set to `timeline`, the following UI transformations occur: +- Message bubbles are hidden, and messages span the entire width of the container. +- Consecutive messages from the same sender are clustered with tighter vertical spacing. +- An emerald left-accent border highlights the active message on hover. + +### 3. Visual Badging & Indicators +- **Header Pill**: If a room contains federation metadata, a globe icon alongside a green pill (`matrix:origin-server`) is rendered in the `ChatHeader`. +- **Message Sender Badge**: External federated users are identified using the format `@username:homeserver.org`. If the sender's homeserver differs from the local homeserver, a subtle green outlined badge displaying the remote domain (e.g. `matrix.org`) is shown next to their display name. + +### 4. Dynamic Federation Joining +When a user attempts to join a room, `joinRoom` in `EmbeddedChatApi.ts` checks the room's metadata: +- If the room is detected as federated (`isFederated` is true) and has a valid Matrix Room ID (`mrid`), the client routes the request via: + ```http + POST /api/v1/federation/joinExternalPublicRoom + ``` +- This ensures that local users self-joining a federated room are correctly registered across the Matrix homeserver network and prevent subscription desynchronization. + +--- + +## Testing in Development + +You can test the Matrix integration via Storybook. Run: + +```bash +yarn storybook +``` + +In the Storybook sidebar, navigate to **EmbeddedChat / WithMatrix**. The mock configuration demonstrates: +- A pre-configured federated room ID. +- Element color theme and timeline mode active. +- Correct rendering of external message badges and federated header states. diff --git a/packages/docs/sidebars.js b/packages/docs/sidebars.js index 1451557e64..db32901b11 100644 --- a/packages/docs/sidebars.js +++ b/packages/docs/sidebars.js @@ -38,6 +38,7 @@ const sidebars = { 'Usage/theming', 'Usage/authentication', 'Usage/ec_rc_setup', + 'Usage/matrix', ], }, ], diff --git a/packages/react/src/stories/EmbeddedChatWithMatrix.stories.js b/packages/react/src/stories/EmbeddedChatWithMatrix.stories.js index cd5ceab5d6..c90c4d15d3 100644 --- a/packages/react/src/stories/EmbeddedChatWithMatrix.stories.js +++ b/packages/react/src/stories/EmbeddedChatWithMatrix.stories.js @@ -18,6 +18,8 @@ export const WithMatrix = { auth: { flow: 'PASSWORD', }, - dark: false, + theme: 'matrix', + layoutMode: 'timeline', + dark: true, }, }; diff --git a/packages/react/src/theme/MatrixTheme.js b/packages/react/src/theme/MatrixTheme.js new file mode 100644 index 0000000000..b41647f51e --- /dev/null +++ b/packages/react/src/theme/MatrixTheme.js @@ -0,0 +1,104 @@ +const MatrixTheme = { + radius: '0.2rem', + commonColors: { + black: 'hsl(0, 100%, 0%)', + white: 'hsl(0, 100%, 100%)', + }, + schemes: { + light: { + background: 'hsl(0, 0%, 100%)', + foreground: 'hsl(216, 18%, 10%)', + card: 'hsl(0, 0%, 100%)', + cardForeground: 'hsl(216, 18%, 10%)', + popover: 'hsl(0, 0%, 100%)', + popoverForeground: 'hsl(216, 18%, 10%)', + primary: 'hsl(163, 88%, 41%)', + primaryForeground: 'hsl(0, 0%, 100%)', + secondary: 'hsl(163, 40%, 96%)', + secondaryForeground: 'hsl(163, 88%, 20%)', + muted: 'hsl(163, 20%, 96%)', + mutedForeground: 'hsl(216, 10%, 45%)', + accent: 'hsl(163, 40%, 94%)', + accentForeground: 'hsl(163, 88%, 25%)', + destructive: 'hsl(0, 84.2%, 60.2%)', + destructiveForeground: 'hsl(0, 0%, 98%)', + border: 'hsl(163, 20%, 90%)', + input: 'hsl(163, 20%, 90%)', + ring: 'hsl(163, 88%, 41%)', + warning: 'hsl(38, 92%, 50%)', + warningForeground: 'hsl(48, 96%, 89%)', + success: 'hsl(163, 88%, 41%)', + successForeground: 'hsl(0, 0%, 100%)', + info: 'hsl(214, 76.4%, 50.2%)', + infoForeground: 'hsl(214.3, 77.8%, 92.9%)', + }, + dark: { + background: 'hsl(216, 18%, 10%)', + foreground: 'hsl(210, 40%, 98%)', + card: 'hsl(216, 18%, 12%)', + cardForeground: 'hsl(210, 40%, 98%)', + popover: 'hsl(216, 18%, 10%)', + popoverForeground: 'hsl(210, 40%, 98%)', + primary: 'hsl(163, 88%, 41%)', + primaryForeground: 'hsl(0, 0%, 100%)', + secondary: 'hsl(216, 15%, 15%)', + secondaryForeground: 'hsl(210, 40%, 98%)', + muted: 'hsl(216, 15%, 15%)', + mutedForeground: 'hsl(216, 10%, 65%)', + accent: 'hsl(163, 30%, 15%)', + accentForeground: 'hsl(163, 88%, 41%)', + destructive: 'hsl(0, 62.8%, 30.6%)', + destructiveForeground: 'hsl(0, 0%, 98%)', + border: 'hsl(216, 15%, 20%)', + input: 'hsl(216, 15%, 20%)', + ring: 'hsl(163, 88%, 41%)', + warning: 'hsl(48, 96%, 89%)', + warningForeground: 'hsl(38, 92%, 50%)', + success: 'hsl(163, 88%, 41%)', + successForeground: 'hsl(0, 0%, 100%)', + info: 'hsl(214.3, 77.8%, 92.9%)', + infoForeground: 'hsl(214.4, 75.8%, 19.4%)', + }, + }, + typography: { + default: { + fontFamily: '"Inter", "Helvetica Neue", sans-serif', + fontSize: 14, + fontWeightLight: 300, + fontWeightRegular: 400, + fontWeightMedium: 500, + fontWeightBold: 700, + }, + h1: { + fontSize: '2.625rem', + fontWeight: 800, + }, + h2: { + fontSize: '1.875rem', + fontWeight: 800, + }, + h3: { + fontSize: '1.5rem', + fontWeight: 400, + }, + h4: { + fontSize: '1.25rem', + fontWeight: 400, + }, + h5: { + fontSize: '1rem', + fontWeight: 400, + }, + h6: { + fontSize: '0.875rem', + fontWeight: 500, + }, + }, + shadows: [ + 'none', + 'rgba(17, 17, 26, 0.05) 0px 1px 0px, rgba(17, 17, 26, 0.1) 0px 0px 8px', + 'rgba(100, 100, 111, 0.2) 0px 7px 29px 0px', + ], +}; + +export default MatrixTheme; diff --git a/packages/react/src/views/ChatHeader/ChatHeader.js b/packages/react/src/views/ChatHeader/ChatHeader.js index 0fcc7a7a5a..b12b3f2bc2 100644 --- a/packages/react/src/views/ChatHeader/ChatHeader.js +++ b/packages/react/src/views/ChatHeader/ChatHeader.js @@ -32,6 +32,26 @@ import getChatHeaderStyles from './ChatHeader.styles'; import useSetExclusiveState from '../../hooks/useSetExclusiveState'; import SurfaceMenu from '../SurfaceMenu/SurfaceMenu'; + + +const GlobeIcon = ({ size }) => ( + + + + + +); + const ChatHeader = ({ isClosable, setClosableState, @@ -84,7 +104,9 @@ const ChatHeader = ({ const workspaceLevelRoles = useUserStore((state) => state.roles); const { RCInstance, ECOptions } = useRCContext(); - const { channelName, anonymousMode, showRoles } = ECOptions ?? {}; + const { channelName, anonymousMode, showRoles, theme: configTheme } = ECOptions ?? {}; + + const isFederated = channelInfo?.federated || configTheme === 'matrix'; const isUserAuthenticated = useUserStore( (state) => state.isUserAuthenticated @@ -390,16 +412,20 @@ const ChatHeader = ({ css={styles.channelName} onClick={() => setExclusiveState(setShowChannelinfo)} > - + {isFederated ? ( + + ) : ( + + )}
{ secure = false, dark = false, remoteOpt = false, + layoutMode = 'bubble', } = config; const auth = useMemo( @@ -244,6 +246,7 @@ const EmbeddedChat = (props) => { showUsername, hideHeader, anonymousMode, + layoutMode, }), [ enableThreads, @@ -260,6 +263,7 @@ const EmbeddedChat = (props) => { showUsername, hideHeader, anonymousMode, + layoutMode, ] ); @@ -268,14 +272,21 @@ const EmbeddedChat = (props) => { [RCInstance, ECOptions] ); + const resolvedTheme = useMemo(() => { + if (theme === 'matrix') { + return MatrixTheme; + } + return theme || DefaultTheme; + }, [theme]); + if (!isSynced) return null; return ( - + { const isStarred = @@ -234,7 +235,7 @@ const Message = ({ { +export const getMessageStyles = ({ theme, mode }) => { const styles = { main: css` display: flex; @@ -15,6 +15,26 @@ export const getMessageStyles = ({ theme }) => { padding-left: 0.8rem; } `, + timelineMain: css` + display: flex; + flex-direction: row; + align-items: flex-start; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 2.25rem; + padding-right: 2.25rem; + color: ${theme.colors.foreground}; + border-left: 3px solid transparent; + &:hover { + border-left: 3px solid ${theme.colors.primary}; + background-color: ${mode === 'light' + ? 'rgba(13, 189, 139, 0.05)' + : 'rgba(13, 189, 139, 0.1)'}; + } + @media (max-width: 768px) { + padding-left: 0.8rem; + } + `, messageEditing: css` background-color: ${theme.colors.secondary}; &:hover { diff --git a/packages/react/src/views/Message/MessageHeader.js b/packages/react/src/views/Message/MessageHeader.js index a4bff59b10..1e135c5308 100644 --- a/packages/react/src/views/Message/MessageHeader.js +++ b/packages/react/src/views/Message/MessageHeader.js @@ -1,4 +1,5 @@ import React from 'react'; +import { css } from '@emotion/react'; import PropTypes from 'prop-types'; import { format } from 'date-fns'; import { @@ -27,6 +28,9 @@ const MessageHeader = ({ const { theme } = useTheme(); const styles = getMessageHeaderStyles(theme); const getDisplayNameColor = useDisplayNameColor(); + const username = message.u?.username || ''; + const isFederatedUser = username.includes(':'); + const serverDomain = isFederatedUser ? username.split(':')[1] : null; const authenticatedUserId = useUserStore((state) => state.userId); const showUsername = ECOptions?.showUsername; @@ -135,6 +139,25 @@ const MessageHeader = ({ @{message.u.username} )} + {serverDomain && ( + + {serverDomain} + + )} {!message.t && ECOptions?.showRoles && isRoles && ( <> {admins?.includes(message?.u?.username) && ( From 737db318471f68b2266fb0a9429bf684e6485154 Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Sun, 12 Jul 2026 20:53:12 +0530 Subject: [PATCH 3/5] formatted with prettier --- packages/react/src/views/ChatHeader/ChatHeader.js | 9 ++++++--- packages/react/src/views/EmbeddedChat.js | 7 ++++++- packages/react/src/views/Message/Message.js | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/react/src/views/ChatHeader/ChatHeader.js b/packages/react/src/views/ChatHeader/ChatHeader.js index b12b3f2bc2..781d4f3361 100644 --- a/packages/react/src/views/ChatHeader/ChatHeader.js +++ b/packages/react/src/views/ChatHeader/ChatHeader.js @@ -32,8 +32,6 @@ import getChatHeaderStyles from './ChatHeader.styles'; import useSetExclusiveState from '../../hooks/useSetExclusiveState'; import SurfaceMenu from '../SurfaceMenu/SurfaceMenu'; - - const GlobeIcon = ({ size }) => ( state.roles); const { RCInstance, ECOptions } = useRCContext(); - const { channelName, anonymousMode, showRoles, theme: configTheme } = ECOptions ?? {}; + const { + channelName, + anonymousMode, + showRoles, + theme: configTheme, + } = ECOptions ?? {}; const isFederated = channelInfo?.federated || configTheme === 'matrix'; diff --git a/packages/react/src/views/EmbeddedChat.js b/packages/react/src/views/EmbeddedChat.js index 029d1fe19b..237fdb5a3f 100644 --- a/packages/react/src/views/EmbeddedChat.js +++ b/packages/react/src/views/EmbeddedChat.js @@ -18,7 +18,12 @@ import { import { ChatLayout } from './ChatLayout'; import { ChatHeader } from './ChatHeader'; import { RCInstanceProvider } from '../context/RCInstance'; -import { useUserStore, useLoginStore, useMessageStore, useChannelStore } from '../store'; +import { + useUserStore, + useLoginStore, + useMessageStore, + useChannelStore, +} from '../store'; import DefaultTheme from '../theme/DefaultTheme'; import MatrixTheme from '../theme/MatrixTheme'; import { getTokenStorage } from '../lib/auth'; diff --git a/packages/react/src/views/Message/Message.js b/packages/react/src/views/Message/Message.js index c95f164335..445c9cd4d9 100644 --- a/packages/react/src/views/Message/Message.js +++ b/packages/react/src/views/Message/Message.js @@ -109,7 +109,9 @@ const Message = ({ const isTimeline = ECOptions?.layoutMode === 'timeline'; const variantStyles = - !isTimeline && !isInSidebar && variantOverrides === 'bubble' ? bubbleStyles : {}; + !isTimeline && !isInSidebar && variantOverrides === 'bubble' + ? bubbleStyles + : {}; const handleStarMessage = async (msg) => { const isStarred = @@ -235,7 +237,9 @@ const Message = ({ Date: Sun, 12 Jul 2026 21:51:47 +0530 Subject: [PATCH 4/5] doc: add Matrix Integration reference to main README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2c68bde9d8..4d24fe4992 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,8 @@ This environment offers a complete setup for developing and testing the `Embedde - Theming Technical: [Technical Overview](https://rocketchat.github.io/EmbeddedChat/docs/docs/Development/theming_technical) – Technical aspects of theming. + - Matrix Integration: [Guide](https://rocketchat.github.io/EmbeddedChat/docs/docs/Usage/matrix) – Configure Matrix Federation in Embedded Chat. + ### Contributors From 2bdd21f94944b22fab22c42b4d955b480efe4d67 Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Sun, 12 Jul 2026 22:05:59 +0530 Subject: [PATCH 5/5] doc: update matrix.md based on review feedback --- packages/docs/docs/Usage/matrix.md | 54 +++++++++--------------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/packages/docs/docs/Usage/matrix.md b/packages/docs/docs/Usage/matrix.md index 8a4229ebbc..850dbec700 100644 --- a/packages/docs/docs/Usage/matrix.md +++ b/packages/docs/docs/Usage/matrix.md @@ -52,44 +52,22 @@ function App() { --- -## Architectural Details +## User Experience & Customization -### 1. Matrix Theme (`MatrixTheme.js`) -The Matrix Theme configures a tailored HSL color palette mapping exactly to Element Web's color tokens: -- **Brand Green**: `hsl(163, 88%, 40%)` (`#0dbd8b`) -- **Dark Background**: `hsl(216, 17%, 10%)` (`#15191e`) -- **Message Cards / Popovers**: `hsl(220, 12%, 20%)` (`#2c3038`) -- **Border / Outline Lines**: `hsl(215, 11%, 25%)` (`#394049`) +### 1. Matrix Theme Customization +When integrating the widget, using `theme="matrix"` automatically applies the HSL palette inspired by Element Web: +- **Brand Accent (Emerald Green)**: `#0dbd8b` (`hsl(163, 88%, 41%)`) +- **Dark Mode Background (Charcoal Slate)**: `#15191e` (`hsl(216, 18%, 10%)`) +- **Light Mode Background (Pure White)**: `#ffffff` +- **Typography**: Clean, readable sans-serif layout using `"Inter", "Helvetica Neue", sans-serif` for all UI components. ### 2. Timeline Layout Mode -When `layoutMode` is set to `timeline`, the following UI transformations occur: -- Message bubbles are hidden, and messages span the entire width of the container. -- Consecutive messages from the same sender are clustered with tighter vertical spacing. -- An emerald left-accent border highlights the active message on hover. - -### 3. Visual Badging & Indicators -- **Header Pill**: If a room contains federation metadata, a globe icon alongside a green pill (`matrix:origin-server`) is rendered in the `ChatHeader`. -- **Message Sender Badge**: External federated users are identified using the format `@username:homeserver.org`. If the sender's homeserver differs from the local homeserver, a subtle green outlined badge displaying the remote domain (e.g. `matrix.org`) is shown next to their display name. - -### 4. Dynamic Federation Joining -When a user attempts to join a room, `joinRoom` in `EmbeddedChatApi.ts` checks the room's metadata: -- If the room is detected as federated (`isFederated` is true) and has a valid Matrix Room ID (`mrid`), the client routes the request via: - ```http - POST /api/v1/federation/joinExternalPublicRoom - ``` -- This ensures that local users self-joining a federated room are correctly registered across the Matrix homeserver network and prevent subscription desynchronization. - ---- - -## Testing in Development - -You can test the Matrix integration via Storybook. Run: - -```bash -yarn storybook -``` - -In the Storybook sidebar, navigate to **EmbeddedChat / WithMatrix**. The mock configuration demonstrates: -- A pre-configured federated room ID. -- Element color theme and timeline mode active. -- Correct rendering of external message badges and federated header states. +Configuring `layoutMode="timeline"` replaces standard message bubbles with a flat, continuous design: +- Message bubbles are removed, allowing messages to span the full width of the container. +- Consecutive messages from the same sender are clustered together under a single name/timestamp header with reduced vertical padding. +- Active messages are highlighted on hover with a theme-colored left border. + +### 3. Federated Room & User Badges +To help users identify federated communication: +- **Federated Room Indicator**: A globe icon is displayed in the widget header in place of the standard `#` or private lock icon to show the room is federated. +- **Server Origin Badges**: External federated users (usernames containing colons, e.g. `@user:matrix.org`) automatically get an emerald-bordered badge next to their username showing their remote domain (e.g. `matrix.org`).