Skip to content
kerfjsGitHub

PresenceStack

Who is here now: a row of avatars in each person's colour.

SourcePackage @kerf-js/veneer

Preview

tsx
import { PresenceStack } from '@kerf-js/veneer'import type { Peer } from '@kerf-js/veneer'export function Overflow() {  return (  <PresenceStack    peers={PEERS}    max={3}  />  )}const PEOPLE = [  {    id: 'mara',    name: 'Mara Lindqvist',    email: 'mara@kerf.dev',    role: 'Owner',    team: 'Platform',    initials: 'ML',  },  {    id: 'jun',    name: 'Jun Tanaka',    email: 'jun@kerf.dev',    role: 'Admin',    team: 'Platform',    initials: 'JT',  },  {    id: 'priya',    name: 'Priya Raman',    email: 'priya@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'PR',  },  {    id: 'tomas',    name: 'Tomás Oliveira',    email: 'tomas@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'TO',  },  {    id: 'lena',    name: 'Lena Fischer',    email: 'lena@kerf.dev',    role: 'Member',    team: 'Data',    initials: 'LF',  },  {    id: 'noor',    name: 'Noor Haddad',    email: 'noor@kerf.dev',    role: 'Viewer',    team: 'Data',    initials: 'NH',  },  {    id: 'sam',    name: 'Sam Okafor',    email: 'sam@kerf.dev',    role: 'Member',    team: 'Mobile',    initials: 'SO',  },  {    id: 'ines',    name: 'Inés Vidal',    email: 'ines@kerf.dev',    role: 'Viewer',    team: 'Mobile',    initials: 'IV',  },]const PEERS: Peer[] = PEOPLE.map((p, i) => ({  id: p.id,  name: p.name,  color: i + 1,}))

Installation

npm install @kerf-js/veneer

Usage

tsx
import { PresenceStack } from '@kerf-js/veneer'<PresenceStack  peers={PEERS}  max={3}/>

People coming and going

Joins and leaves are announced as one summary.

tsx
import { state } from '@kerf-js/core'import {  Button,  PresenceStack,  Row,  Stack} from '@kerf-js/veneer'import type { Peer } from '@kerf-js/veneer'export function Room() {  const peers = state(PEERS.slice(0, 3))  const join = () =>    (peers.value = PEERS.slice(0, peers.value.length + 1))  const leave = () =>    (peers.value = peers.value.slice(0, -1))  return (    <Stack      gap="related"      align="center"    >      <PresenceStack        peers={peers}        max={4}      />      <Row gap="tight">        <Button          size="sm"          emphasis="outline"          tone="neutral"          aria-disabled={            peers.value.length === PEERS.length              ? 'true'              : undefined          }          onClick={join}        >          Someone joins        </Button>        <Button          size="sm"          emphasis="ghost"          tone="neutral"          aria-disabled={            peers.value.length < 2 ? 'true' : undefined          }          onClick={leave}        >          Someone leaves        </Button>      </Row>    </Stack>  )}const PEOPLE = [  {    id: 'mara',    name: 'Mara Lindqvist',    email: 'mara@kerf.dev',    role: 'Owner',    team: 'Platform',    initials: 'ML',  },  {    id: 'jun',    name: 'Jun Tanaka',    email: 'jun@kerf.dev',    role: 'Admin',    team: 'Platform',    initials: 'JT',  },  {    id: 'priya',    name: 'Priya Raman',    email: 'priya@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'PR',  },  {    id: 'tomas',    name: 'Tomás Oliveira',    email: 'tomas@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'TO',  },  {    id: 'lena',    name: 'Lena Fischer',    email: 'lena@kerf.dev',    role: 'Member',    team: 'Data',    initials: 'LF',  },  {    id: 'noor',    name: 'Noor Haddad',    email: 'noor@kerf.dev',    role: 'Viewer',    team: 'Data',    initials: 'NH',  },  {    id: 'sam',    name: 'Sam Okafor',    email: 'sam@kerf.dev',    role: 'Member',    team: 'Mobile',    initials: 'SO',  },  {    id: 'ines',    name: 'Inés Vidal',    email: 'ines@kerf.dev',    role: 'Viewer',    team: 'Mobile',    initials: 'IV',  },]const PEERS: Peer[] = PEOPLE.map((p, i) => ({  id: p.id,  name: p.name,  color: i + 1,}))

Accessibility

  • This has no shortcuts of its own: controls inside it use Tab, Enter and Space as usual, and veneer's parts keep their roles and labels.

API Reference

PropTypeDefaultDescription
peersLive<Peer[]>—Who is here.
maxnumber4How many avatars before +n.
followingstring | null—The id of the person being followed.
labelstring'People here'Names the group.
EventTypeDefaultDescription
onFollow((p: Peer | null) => void)—Turns each avatar into a follow toggle.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="avatar-stack"] [data-part="sr-only"]

sr-onlymorecontent

RelatedFollowingCursorsSection peers