Appearance
Building Custom UI
If the built-in widget doesn't fit your design, use PulseClient directly to build your own collaboration UI.
When to Go Custom
- You need the collaboration data in a different layout
- You want to embed comments inside your existing UI (not a floating panel)
- You only need some features (e.g., just presence, no comments)
React Example: Comments
tsx
import { PulseClient } from '@gamention/pulse-core';
import type { Thread } from '@gamention/pulse-shared';
import { useEffect, useState } from 'react';
function useThreads(client: PulseClient) {
const [threads, setThreads] = useState<Thread[]>([]);
useEffect(() => {
setThreads(client.state.threads);
return client.state.on('threads', setThreads);
}, [client]);
return threads;
}
function Comments({ client }: { client: PulseClient }) {
const threads = useThreads(client);
const [text, setText] = useState('');
return (
<div className="comments">
{threads.map(thread => (
<div key={thread.id}>
{thread.comments.map(c => (
<div key={c.id}>
<strong>{client.state.getUser(c.userId)?.name}</strong>
<p>{c.body}</p>
</div>
))}
<input
value={text}
onChange={e => setText(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter' && text.trim()) {
client.reply(thread.id, text);
setText('');
}
}}
placeholder="Reply..."
/>
</div>
))}
</div>
);
}React Example: Presence Bar
tsx
import type { PresenceUser } from '@gamention/pulse-shared';
function PresenceBar({ client }: { client: PulseClient }) {
const [users, setUsers] = useState<PresenceUser[]>([]);
useEffect(() => {
setUsers(client.state.presence);
return client.state.on('presence', setUsers);
}, [client]);
return (
<div className="presence">
{users.map(({ user, status }) => (
<div
key={user.id}
className={`avatar ${status}`}
style={{ background: user.color }}
title={`${user.name} (${status})`}
>
{user.avatar ? <img src={user.avatar} /> : user.name[0]}
</div>
))}
</div>
);
}Lifecycle
Always clean up when the component unmounts:
tsx
function App() {
const [client, setClient] = useState<PulseClient | null>(null);
useEffect(() => {
const c = new PulseClient({
apiKey: 'pk_...',
token: userToken,
room: 'my-room',
endpoint: 'wss://pulse.hire.rest'
});
c.connect();
setClient(c);
return () => c.disconnect();
}, []);
if (!client) return null;
return (
<>
<PresenceBar client={client} />
<Comments client={client} />
</>
);
}Mixing Widget + Custom UI
You can use <pulse-widget> for most features and only build custom components for specific parts. The widget and PulseClient are independent — just make sure they connect to the same room.