People directory
Searchable list of people with presence.
Directory
8 people · 3 available now
Your favourites
- Ahmed Raza In a meeting until 15:00
- Mia Jensen Available
- Yuki Tanaka Available
Everyone
8 results
Design
- Senior product designer · London · Available
Engineering
- Engineering manager · Lahore · In a meeting until 15:00
- Frontend engineer · Madrid · Away, back at 14:30
Finance
- Data analyst · Paris · Focus time
People Ops
- Payroll specialist · Lisbon · On leave until 20 Oct
- Recruiter · Tokyo · Available
Product
- Product manager · Copenhagen · Available
Customer
- Support lead · Dubai · Invitation sent
people-directory.tsx
"use client";
import { useState } from "react";
import { LuSearchX } from "react-icons/lu";
import {
Avatar,
AvatarLabel,
Button,
EmptyState,
ListItem,
Presence,
SearchField,
} from "@nexera-ui/react";
// Sample data
interface Person {
id: string;
name: string;
initials: string;
role: string;
team: string;
email: string;
location: string;
status: "Active" | "On leave" | "Remote" | "Invited";
}
const PEOPLE: Person[] = [
{
id: "p1",
name: "Ayesha Khan",
initials: "AK",
role: "Senior product designer",
team: "Design",
email: "ayesha.khan@northwind.io",
location: "London",
status: "Active",
},
{
id: "p2",
name: "Sara Malik",
initials: "SM",
role: "Payroll specialist",
team: "People Ops",
email: "sara.malik@northwind.io",
location: "Lisbon",
status: "On leave",
},
{
id: "p3",
name: "Ahmed Raza",
initials: "AR",
role: "Engineering manager",
team: "Engineering",
email: "ahmed.raza@northwind.io",
location: "Lahore",
status: "Remote",
},
{
id: "p4",
name: "Mia Jensen",
initials: "MJ",
role: "Product manager",
team: "Product",
email: "mia.jensen@northwind.io",
location: "Copenhagen",
status: "Active",
},
{
id: "p5",
name: "Luis Ortega",
initials: "LO",
role: "Frontend engineer",
team: "Engineering",
email: "luis.ortega@northwind.io",
location: "Madrid",
status: "Active",
},
{
id: "p6",
name: "Yuki Tanaka",
initials: "YT",
role: "Recruiter",
team: "People Ops",
email: "yuki.tanaka@northwind.io",
location: "Tokyo",
status: "Active",
},
{
id: "p7",
name: "Chloe Martin",
initials: "CM",
role: "Data analyst",
team: "Finance",
email: "chloe.martin@northwind.io",
location: "Paris",
status: "Remote",
},
{
id: "p8",
name: "Omar Haddad",
initials: "OH",
role: "Support lead",
team: "Customer",
email: "omar.haddad@northwind.io",
location: "Dubai",
status: "Invited",
},
];
const TEAMS = [
"Design",
"Engineering",
"Finance",
"People Ops",
"Product",
"Customer",
] as const;
type Status = "online" | "away" | "busy" | "offline";
const PRESENCE: Record<string, { status: Status; note: string }> = {
p1: { status: "online", note: "Available" },
p2: { status: "offline", note: "On leave until 20 Oct" },
p3: { status: "busy", note: "In a meeting until 15:00" },
p4: { status: "online", note: "Available" },
p5: { status: "away", note: "Away, back at 14:30" },
p6: { status: "online", note: "Available" },
p7: { status: "busy", note: "Focus time" },
p8: { status: "offline", note: "Invitation sent" },
};
const presenceOf = (id: string) => PRESENCE[id] ?? { status: "offline" as const, note: "Offline" };
const FAVOURITES = ["p3", "p4", "p6"];
/** People directory: search everyone at Northwind by name, role or team and see who is available right now. */
export default function PeopleDirectory() {
const [query, setQuery] = useState("");
const q = query.trim().toLowerCase();
const found = PEOPLE.filter((p) =>
`${p.name} ${p.role} ${p.team} ${p.location}`.toLowerCase().includes(q),
);
const online = PEOPLE.filter((p) => presenceOf(p.id).status === "online").length;
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<div className="mx-auto grid max-w-3xl gap-6">
<header className="grid gap-1">
<h2 className="text-heading-h2 text-primary">Directory</h2>
<p className="text-body-default text-secondary">
{PEOPLE.length} people · {online} available now
</p>
</header>
<section aria-labelledby="dir-favourites" className="grid gap-3">
<h3 id="dir-favourites" className="text-heading-h4 text-primary">
Your favourites
</h3>
<ul className="flex flex-wrap gap-x-6 gap-y-3">
{PEOPLE.filter((p) => FAVOURITES.includes(p.id)).map((p) => (
<li key={p.id}>
<AvatarLabel
name={p.name}
subtitle={presenceOf(p.id).note}
size="md"
avatar={
<Avatar
initials={p.initials}
isDecorative
presence={<Presence status={presenceOf(p.id).status} isDecorative />}
/>
}
/>
</li>
))}
</ul>
</section>
<section aria-labelledby="dir-everyone" className="grid gap-3">
<h3 id="dir-everyone" className="text-heading-h4 text-primary">
Everyone
</h3>
<SearchField
label="Search people"
placeholder="Name, role, team or city"
value={query}
onChange={setQuery}
/>
<p className="text-body-small text-muted" aria-live="polite">
{found.length} {found.length === 1 ? "result" : "results"}
</p>
{found.length === 0 ? (
<div className="py-12">
<EmptyState
icon={<LuSearchX />}
title={`No one called “${query}”`}
description="Check the spelling, or search by team or city."
headingLevel={4}
secondaryAction={
<Button
variant="secondary"
size="sm"
onPress={() => {
setQuery("");
}}
>
Clear search
</Button>
}
/>
</div>
) : (
TEAMS.filter((team) => found.some((p) => p.team === team)).map((team) => (
<div key={team} className="grid gap-1">
<h4 className="text-component-label text-muted">{team}</h4>
<ul className="grid divide-y divide-subtle rounded-lg border border-default bg-surface">
{found
.filter((p) => p.team === team)
.map((p) => (
<li key={p.id}>
<ListItem
href={`#person-${p.id}`}
title={p.name}
subtitle={`${p.role} · ${p.location} · ${presenceOf(p.id).note}`}
leading={
<Avatar
size="lg"
initials={p.initials}
isDecorative
presence={<Presence status={presenceOf(p.id).status} isDecorative />}
/>
}
/>
</li>
))}
</ul>
</div>
))
)}
</section>
</div>
</div>
);
}
More lists, tables and records blocks
- Data tableSearch, sort, select rows and page through results.
- Bulk actionsA floating bar that acts on the selected rows.
- Filter barActive filters as removable chips above results.
- Filters drawerDetailed filters in a side panel.
- Record details drawerOpen a row to see details without leaving the list.
- Activity feedNewest first, with a 'new items' pill and paging.
- File browserFolders and files in a tree.
- Keyboard shortcutsA modal listing every shortcut.