Blocks
Team
The people page every product needs: who's on the team, what they can do, and who's been invited. Invite several people at once, change a role in place, hand over ownership, and watch your seats.
Examples
The translators of Bayt al-Hikma
Invite people (paste several addresses at once), change a role, or open a member's menu. Changing Qusta's role fails, to show it going back with a message. On a phone each person becomes a small card.
Translators
- Hunayn ibn Ishaq You hunayn@bayt-al-hikma.orgOwner Make someone else the owner to step down.Active 5 minutes ago
- Thabit ibn Qurra thabit@bayt-al-hikma.orgActive 3 hours ago
- Hubaysh al-Asam hubaysh@bayt-al-hikma.orgActive 2 days ago
- Ishaq ibn Hunayn ishaq@bayt-al-hikma.orgActive last week
- Qusta ibn Luqa qusta@bayt-al-hikma.orgNot active yet
<script lang="ts">
import Team, { type TeamInvite, type TeamMember } from '#lib/blocks/team/Team.svelte';
const day = 86_400_000;
let members = $state<TeamMember[]>([
{
id: 'm1',
name: 'Hunayn ibn Ishaq',
email: 'hunayn@bayt-al-hikma.org',
role: 'owner',
lastActive: Date.now() - 5 * 60_000,
you: true
},
{
id: 'm2',
name: 'Thabit ibn Qurra',
email: 'thabit@bayt-al-hikma.org',
role: 'admin',
lastActive: Date.now() - 3 * 3_600_000
},
{
id: 'm3',
name: 'Hubaysh al-Asam',
email: 'hubaysh@bayt-al-hikma.org',
role: 'member',
lastActive: Date.now() - 2 * day
},
{
id: 'm4',
name: 'Ishaq ibn Hunayn',
email: 'ishaq@bayt-al-hikma.org',
role: 'member',
lastActive: Date.now() - 9 * day
},
{ id: 'm5', name: 'Qusta ibn Luqa', email: 'qusta@bayt-al-hikma.org', role: 'viewer' }
]);
let invites = $state<TeamInvite[]>([
{ id: 'i1', email: 'al-kindi@bayt-al-hikma.org', role: 'member', sent: Date.now() - day }
]);
// Stand-ins for your API, with a little latency. Changing Qusta's role fails, to show the rollback.
const wait = () => new Promise<void>((r) => setTimeout(r, 500));
const fail = (code: string) => Object.assign(new Error(code), { code });
</script>
<Team
bind:members
bind:invites
seats={8}
inviteLink="https://bayt-al-hikma.org/join/translators-7f3a"
title="Translators"
oninvite={wait}
onrolechange={async (m) => {
await wait();
if (m.id === 'm5') throw fail('network');
}}
onremove={wait}
ontransfer={wait}
onresend={wait}
onrevoke={wait}
/>Installation
npx sinaui add blocks/teamCopy src/lib/blocks/team/, with AlertDialog, Avatar, Badge, Button, CopyButton, Dialog, dropdown/, EmptyState, Icon, Meter, RadioCards, SearchField, Segmented, Select, TagInput, Textarea, toast/, motion.ts, optimistic.ts and tokens.css. No dependencies.
Props
| Prop | Type | Default |
|---|---|---|
members | { id, name, email, role, avatar?, lastActive?, you? }[] (bindable) | required |
invites | { id, email, role, sent }[] (bindable) | [] |
seats | number: members and pending invites each take one | required |
inviteLink | string: offered as "Copy invite link" | |
title | string | 'Team' |
oninvite | (emails, role, message) => Promise<invites | void> | |
onrolechange | (member, role) => Promise | void | |
onremove | (member) => Promise | void | |
ontransfer | (member) => Promise | void | |
onresend | (invite) => Promise | void | |
onrevoke | (invite) => Promise | void |
Roles are 'owner' | 'admin' | 'member' | 'viewer'. A role change and a withdrawn
invite show at once; if their callback throws, they go back and say so. To show your own wording,
throw an error with a code and add it to the block's messages; codes seat_limit, last_owner, already_member, forbidden and network are worded already. Mount <Toaster /> once in your layout to show the messages.
Keyboard
| Key | Does |
|---|---|
| ← → | Switch between members and pending invites |
| Enter or , | Add an email address in the invite form |
| ↑ ↓ | Choose a role in a member's role picker |
| Escape | Close a menu or dialog |
Accessibility
- Each role picker is named for its person ("Role for Thabit ibn Qurra").
- Each actions menu is named for its person or invite.
- Removing someone and handing over ownership ask first, in an alert dialog.
- Every result, good or bad, is announced through a toast.
- The owner can't be demoted from the list; the row says how to step down.