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_000Installation
npm install @kerf-js/veneerUsage
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
| Prop | Type | Default | Description |
|---|---|---|---|
members | readonly Member[] | State<readonly Member[]> | — | Everyone in the workspace, one row each. |
invites | readonly Invite[] | State<readonly Invite[]> | — | Pending invitations, listed under the members. |
me | string | — | The signed-in member, who gets a warning before demoting themselves. |
roles | Role[] | — | The roles to offer. |
owner | string | — | The value of the role that must always have someone in it. |
seats | number | — | Seats on the plan. Pending invites use one each. |
| Event | Type | Default | Description |
|---|---|---|---|
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. |