Skip to content
kerfjsGitHub

MembersTable

Everyone in a workspace, with roles, bulk removal and pending invites.

SourcePackage @kerf-js/veneer

Preview

tsx
import { state } from '@kerf-js/core'import { MembersTable, Toaster } from '@kerf-js/veneer'import type { Invite, Member } from '@kerf-js/veneer'import type { Role } from '@kerf-js/veneer'export function Team() {  const people = state<readonly Member[]>(TEAM_MEMBERS)  const invites = state<readonly Invite[]>(TEAM_INVITES)  const remove = (gone: Member[]) => {    people.value = people.value.filter(      (person) => !gone.includes(person),    )  }  const inviteMembers = (    emails: string[],    role: string,  ) => {    invites.value = [      ...invites.value,      ...emails.map((email) => ({        id: email,        email,        role,        sent: 'Just now',      })),    ]  }  return (    <>      <MembersTable        members={people}        invites={invites}        me="mara"        roles={TEAM_ROLES}        seats={12}        onRoleChange={(m, role) =>          (people.value = people.value.map((x) =>            x.id === m.id ? { ...x, role } : x,          ))        }        onRemove={remove}        onRestore={(back) =>          (people.value = [...people.value, ...back])        }        onInvite={inviteMembers}        onRevoke={(invite) =>          (invites.value = invites.value.filter(            (x) => x !== invite,          ))        }      />      <Toaster />    </>  )}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 TEAM_ROLES: Role[] = [  { value: 'owner', label: 'Owner', manages: true },  { value: 'admin', label: 'Admin', manages: true },  { value: 'member', label: 'Member' },  { value: 'viewer', label: 'Viewer' },]const LAST_SEEN = [  'Active now',  '12 min ago',  '1 h ago',  'Today',  'Yesterday',  'Oct 28',  'Oct 24',  'Oct 12',]const TEAM_MEMBERS: Member[] = PEOPLE.map(  (p, i) => ({    id: p.id,    name: p.name,    email: p.email,    role: p.role.toLowerCase(),    lastActive: LAST_SEEN[i],  }),)const TEAM_INVITES: Invite[] = [  {    id: 'dara',    email: 'dara@kerf.dev',    role: 'member',    sent: '2 days ago',  },  {    id: 'kai',    email: 'kai@contract.dev',    role: 'viewer',    sent: 'Oct 27',  },]const min = 60_000

Installation

npm install @kerf-js/veneer

Usage

tsx
import { MembersTable } from '@kerf-js/veneer'

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
membersreadonly Member[] | State<readonly Member[]>—Everyone in the workspace, one row each.
invitesreadonly Invite[] | State<readonly Invite[]>—Pending invitations, listed under the members.
mestring—The signed-in member, who gets a warning before demoting themselves.
rolesRole[]—The roles to offer.
ownerstring—The value of the role that must always have someone in it.
seatsnumber—Seats on the plan. Pending invites use one each.
EventTypeDefaultDescription
onRoleChange(member: Member, role: string) => void—Called with the member and the role picked for them.
onRemove(members: Member[]) => void | Promise<void>—Called with the members to remove, after people confirm.
onRestore((members: Member[]) => void)—Undo for a removal.
onInvite(emails: string[], role: string) => void | Promise<void>—Called with the emails and role from the invite dialog.
onResend((invite: Invite) => void)—Called with the invite when Resend is pressed.
onRevoke((invite: Invite) => void)—Takes back a pending invite.

RelatedInvite dialogSeats meterRoles matrix