@meldkit/react-ui
v0.1.2
Published
Ready-made collaboration UI for @meldkit/react: presence avatars, connection status, remote carets, live pointer cursors
Readme
@meldkit/react-ui
Ready-made collaboration UI for @meldkit/react: presence avatars, a connection
indicator, and remote carets.
Entirely optional. The SDK has no idea this package exists, and everything here is a few dozen lines you could write yourself — the point is that you shouldn't have to write them twice.
npm install @meldkit/react-uiimport {
Avatars,
ConnectionStatus,
CaretOverlay,
useIdentity,
} from '@meldkit/react-ui';
import '@meldkit/react-ui/styles.css';Components
<Avatars /> — who's in the room, as a row of circles with each person's
initials. Reads presence from the provider, so it takes no props in the common
case. Pass me / others to render a list you've already filtered or sorted.
The row is bounded in both directions. Past stackAfter (3) the circles
overlap instead of pushing the layout sideways; past max (5) the remainder
collapses into a +N whose tooltip names them. A room of thirty people is a
real thing, and a presence bar that tries to draw thirty of anything has
stopped being a presence bar.
Your own circle leads the row and carries an accent ring, so it never ends up
inside the overflow count. A green dot marks anyone whose cursor or pointer
says they're active right now.
<ConnectionStatus /> — connection state, and errors. It reports both,
because the two disagree in the case that matters: with the wrong room key the
socket is perfectly healthy and the document is empty, so a plain status dot
would say "connected" next to a blank screen.
<CaretOverlay text={text}>{yourTextarea}</CaretOverlay> — remote carets
over a text input. A textarea can't contain elements, so this wraps yours in a
mirror layer holding the same text with the same font and padding, its own text
transparent, sitting on top. The browser works out where character N lands — no
measurement code, correct through wrapping and resizing.
It draws carets and nothing else. The value, the edits and the CRDT stay yours;
pair it with useSharedText from @meldkit/react.
<InlineCarets text={…} others={…} /> — the same carets in text you render
yourself: a list row, a heading, a cell.
<Cursors space={{ width, height }} /> — everyone else's pointer, drawn
over a shared surface. Reads pointer from presence and places it as a
percentage of its container, so the same position lands in the same place in
windows of different sizes. Its wrapper has to be positioned, and has to have
the same aspect ratio as space.
Motion is the part that takes care. Each cursor moves by transform rather
than left/top, because offsets are a layout change and animating them
re-lays-out and repaints whatever is underneath on every frame. And each glides
for as long as that person's own last gap between updates took, rather than a
fixed duration — too short and a cursor sprints to each position then sits
still until the next one, which is what reads as jitter; too long and it lags
behind the person driving it.
Pass status to put a word next to someone's name — "drawing", "typing" —
from whatever field of your presence answers that.
useIdentity() — a throwaway display name and colour for apps that don't
have accounts yet. Only for display; the userId in your access token is who
you are to MeldKit, and it comes from your server.
Presence shape
The components read three fields:
type CollaboratorPresence = {
name: string;
color: string;
cursor?: number | null; // character offset, not a pixel position
};A floor, not a ceiling — carry whatever else you need alongside them. The to-do
example adds an editing field naming the row someone is in, which is how the
same caret component serves one list row there and a whole document in the text
editor example.
Offsets rather than coordinates is the load-bearing decision: a pixel position means nothing in a window of a different width, an offset means the same thing everywhere.
Styling
Import @meldkit/react-ui/styles.css once, near your entry point. Then, in
order of how much you want to change:
- Retheme with the custom properties (
--mk-accent,--mk-line,--mk-doc-font, …) — set them on:root, or on any ancestor to scope it. - Restyle with your own rules against the
mk-class names, or skip the stylesheet entirely and style the markup yourself.
One caveat, and it's the only part of the CSS that isn't cosmetic: the mirror
layer and the text input must agree on every property that affects where a
character lands, or carets drift further off with every line. Both read the same
--mk-doc-* variables from one rule so they can't diverge. Retheme those rather
than setting font or padding on the textarea directly.
