Skip to main content
Markdown

Making the screens your own

The call screen, the chat and the list of who is online are the Better Messages screens, ready to use. Your app can change them as far as it needs to, in three steps, each going further than the last:

What you changeWhat updates by itself
1. The lookcolours, fonts, corners, wordseverything
2. A pieceone named piece, such as the ring, a tile or the controls, drawn by your own codeeverything around it
3. The codethe whole view, copied into your appwhat runs it: the call, the ring, the devices, reconnecting, encryption

Take the first step that does what you need. The further you go, the more of the screen is yours to keep up to date.

1. The look​

  • Design tokens on the page: --bm-color-accent: 37, 99, 235 (red, green and blue), --bm-messenger-font, --bm-radius-card, --bm-radius-avatar, --bm-color-bg, --bm-color-text-primary and the rest of the Better Messages tokens.
  • Light and dark follow the system. bm-messages-light or bm-messages-dark on <body> keeps one.
  • Words: strings: { 'End Call': 'Hang up' }, by the English text, on <CallScreen strings>, rt.callScreen( { strings } ) or configureUi(). The screens come in English, German, Spanish, French, Italian, Dutch, Polish, Croatian and Hebrew.

2. A piece of your own​

Replace one piece and keep the rest. Your piece gets the same props ours gets, and ours as its children, so it can wrap ours, change around it, or draw something else.

PieceWhat it isIts props
RingCarda call ringing: who calls, answer and rejectuser_id, name, type, onAnswer, onReject
CallStatuswhat a call says of itself: calling, connecting, its timelayout, action, text, duration
VideoTilethe other side of a call between twoparticipant, action
SelfViewyour own camera in its cornertrack
GridTileone person in a group call grid or rosterparticipant, layout
controlsthe call buttons, as a list( buttons, call ) => buttons
OnlineRowa person in the online listmember, mine, actions
Messagea message in the chatmessage, mine, sender, react
Composerwhere the user writessend( text ), typing(), disabled, placeholder, maxLength
EmptyConversationa chat with no messages yet
Avatar, Namea user picture and name on the call screensuser_id, size

In React​

@wordplus/realtime-ui/react takes your pieces in components:

import { CallScreen, CallControl, Chat } from '@wordplus/realtime-ui/react';

<CallScreen rt={ rt } components={ {
RingCard: ( { name, onAnswer, onReject } ) => <MyRing name={ name } answer={ onAnswer } reject={ onReject } />,
VideoTile: ( { children } ) => <div className="my-frame">{ children }</div>,
controls: ( buttons, call ) => [
...buttons,
<CallControl key="raise" icon="raisehand" label="Raise hand" onClick={ () => raise( call ) } />,
],
} } />

<Chat rt={ rt } room="chat:34:42" components={ { EmptyConversation: () => <p>Ask us anything about your order.</p> } } />
  • controls gets our buttons in the dock order and the call (its state, its tracks, what it does), and returns the buttons to show. Drop ours, reorder them, or add your own with CallControl (label, icon, onClick, active). Hanging up stays last, and whatever does not fit folds into More, yours too.
  • A tile you replace keeps its sound: the other side is still heard whatever your tile draws. Draw their picture with the participant tracks: participant.video?.attach( videoElement ).
  • Pieces nest. <PiecesProvider pieces={ … }> gives them to every screen inside it, and the components given to one screen go over them.

On any page: the elements​

The elements (<wordplus-chat>, <wordplus-online>, the call screen) draw in a shadow root of their own. Your stylesheet reaches each piece by its name:

wordplus-calls::part(call-controls) { bottom: 24px; }
wordplus-chat::part(message) { font-size: 15px; }
wordplus-online::part(online-row) { padding-block: 10px; }

The parts: ring-card, call-window, call-status, video-tile, self-view, call-controls, grid-tile, online-row, message, composer and empty-conversation.

To draw a piece with markup of your own, put a <template slot="…"> inside the element. The call screen templates go in a <wordplus-calls> you write on the page:

<wordplus-calls>
<template slot="ring-card">
<div class="ring"><b data-field="name"></b> is calling (<span data-field="type"></span>)
<button data-action="answer">Pick up</button> <button data-action="reject">Not now</button></div>
</template>
<template slot="control" data-control="raise" data-label="Raise hand" data-icon="raisehand"></template>
</wordplus-calls>

<wordplus-chat room="chat:34:42">
<template slot="empty-conversation"><p>Ask us anything about your order.</p></template>
<template slot="message"><div class="line"><b data-field="name"></b>: <span data-field="text"></span></div></template>
</wordplus-chat>

<script>
document.addEventListener( 'wordplus-control', ( e ) => { if ( e.detail.control === 'raise' ) raiseHand(); } );
</script>
  • data-field="…" takes that value as its text, never as markup: name, type, text, time, avatar, role, status.
  • data-src="avatar" takes it as the element src. data-action answers or rejects. data-ours marks where ours goes inside yours.
  • A <style> in the template styles what it draws, inside the element.
  • The templates fill ring-card, call-status, empty-conversation, online-row and message. A control template adds a button to the dock, and the page hears wordplus-control when it is pressed.

3. The code itself​

Copy a view into your app, and change anything in it: its layout, its buttons, how it moves, what it does.

npx @wordplus/realtime-ui add call-screen      # or online-list, chat
  • Only the view is copied. What runs it stays in the package and keeps getting its fixes: the call (usePrivateCall, useGroupCall, the ring in every tab, the devices, reconnecting and encryption) under the call screen, useRoom and useProfiles under the online list, and useChat (history, sending, typing, reactions, reads) under the chat.
  • Where it goes: components/wordplus/<component>/ (--dir for another folder). wordplus-ui.json records the version of each file, and .wordplus-ui/ keeps ours as you copied it. Commit both.
  • What each copy holds:
ComponentIts filesDraw yours
call-screencall-screen.tsx (which layout), private-call.tsx, group-video-call.tsx, group-audio-call.tsx, the grids and their tiles, call-dock.tsx (the control row), device-switch.tsx, add-person.tsx, incoming-call-card.tsx (the ring)<CallScreen rt={ rt } view={ CallView } components={ { RingCard } } />
online-listonline-list.tsx (the list, typing, the count), online-rows.tsx (a row)<OnlineList rt={ rt } room="shop" /> from your copy
chatchat.tsx (the list, the bubbles, the composer)<Chat rt={ rt } room="chat:34:42" /> from your copy
  • On a page without React, add also writes wordplus-ui.elements.tsx and wordplus-ui.build.mjs: node wordplus-ui.build.mjs builds your own elements script, with your copies in it, into public/wordplus-ui.js. Load it in place of ours: connect( { ui: '/wordplus-ui.js' } ).

Checking a change​

  • The tests come with the copy: npx vitest run components/wordplus (with vitest, @testing-library/react and happy-dom). They draw every state from calls and rooms made by hand (@wordplus/realtime-ui/testing).
  • The preview: npx @wordplus/realtime-ui preview serves every component in every state, yours in place of ours: calling out and in, connecting, in a call, a microphone off, a camera refused, group calls as a member and a moderator, the online list, typing, a conversation and an empty one. ?theme=dark, ?dir=rtl and ?width=phone show the rest. It reloads as you save, and --out folder builds it once. It needs esbuild (npm i -D esbuild).
  • With your pieces and your styles (as in a React app, step 2): --pieces app/support/screens.tsx#pieces names the module and export that hold your pieces, --css app/globals.css (once for each stylesheet) adds your styles after ours, and --class shop puts the classes your CSS is scoped under on the page. wordplus-ui.json keeps them, so the next preview needs none. The module holds plain React: what only your framework has, such as the Next.js routing, has no place in it.

When ours changes​

Nothing changes your copy by itself.

  1. npx @wordplus/realtime-ui diff shows what changed in ours since you copied it, file by file, and whether you changed that file too.
  2. npx @wordplus/realtime-ui update takes ours into every file you never changed.
  3. Merge the rest by hand, then run the tests and the preview.
  4. npx @wordplus/realtime-ui accept call-screen records that yours has ours.

What must keep working​

Whatever you change, check these before you ship:

  • Every call state: calling out, ringing in, connecting, in a call, reconnecting, ended, and a group call as a member and as its moderator.
  • A camera or a microphone refused: the call goes on, and says why.
  • Right to left (?dir=rtl), and the width of a phone (?width=phone).
  • Screen readers: every button keeps its label (aria-label, or label on CallControl).
  • Picture in picture and full screen, where the browser has them.
  • Sound: the other side is heard, and your own microphone is never played back to you.