Team availability
Who is free when, side by side.
Team availability
Wednesday 14 October · 08:00 to 18:00 Pakistan time
- Ayesha Khan
- Sara Malik
- Ahmed Raza
- Mia Jensen
- Luis Ortega
- Yuki Tanaka
- Chloe Martin
- Omar Haddad
First shared slot: 16:00 to 17:00
AKARMJLOand 3 more
7 of 8 free · busy or away: Sara
team-availability.tsx
"use client";
import { useState } from "react";
import { LuCalendarPlus } from "react-icons/lu";
import {
AvailabilityRow,
Avatar,
AvatarGroup,
Button,
Segment,
SegmentedControl,
type AvailabilitySegment,
} 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 ZONE = "Asia/Karachi";
/** A time on Wednesday 14 October 2026 in Karachi (UTC+5). */
const at = (hour: number, minute = 0) => new Date(Date.UTC(2026, 9, 14, hour - 5, minute));
const RANGE_START = at(8);
const RANGE_END = at(18);
const SCALE = ["08:00", "10:00", "12:00", "14:00", "16:00", "18:00"];
const BUSY: Record<string, AvailabilitySegment[]> = {
p1: [
{ start: at(9), end: at(10, 30) },
{ start: at(12), end: at(13) },
],
p2: [{ start: at(8), end: at(18), type: "out-of-office" }],
p3: [
{ start: at(9, 30), end: at(9, 45) },
{ start: at(11), end: at(12, 30) },
{ start: at(14), end: at(15) },
],
p4: [
{ start: at(10), end: at(11) },
{ start: at(13, 30), end: at(15) },
],
p5: [
{ start: at(9, 30), end: at(9, 45) },
{ start: at(14), end: at(16) },
],
p6: [
{ start: at(8), end: at(12), type: "out-of-office" },
{ start: at(15), end: at(15, 30) },
],
p7: [{ start: at(11), end: at(12) }],
p8: [],
};
const SLOT = { start: at(16), end: at(17) };
const freeAt = (id: string) =>
!(BUSY[id] ?? []).some((s) => s.start < SLOT.end && s.end > SLOT.start);
const TEAMS = ["Everyone", "Engineering", "People Ops"];
/** Team availability: everyone's busy and away time side by side on one day, with the first slot that suits the team. */
export default function TeamAvailability() {
const [team, setTeam] = useState("Everyone");
const [booked, setBooked] = useState(false);
const people = PEOPLE.filter((p) => team === "Everyone" || p.team === team);
const free = people.filter((p) => freeAt(p.id));
const away = people.filter((p) => !freeAt(p.id));
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<section
aria-labelledby="availability-title"
className="grid gap-5 rounded-xl border border-default bg-surface p-4 shadow-sm @3xl:p-6"
>
<header className="flex flex-wrap items-end justify-between gap-3">
<div className="grid gap-1">
<h2 id="availability-title" className="text-heading-h3 text-primary">
Team availability
</h2>
<p className="text-body-default text-secondary">
Wednesday 14 October · 08:00 to 18:00 Pakistan time
</p>
</div>
<SegmentedControl
aria-label="Team"
selectedKeys={[team]}
onSelectionChange={(keys) => {
setTeam(String([...keys][0] ?? "Everyone"));
}}
>
{TEAMS.map((t) => (
<Segment key={t} id={t} label={t} />
))}
</SegmentedControl>
</header>
<div className="grid gap-1">
<div aria-hidden="true" className="hidden gap-3 @3xl:flex">
<span className="w-45 max-w-1/3 shrink-0" />
<span className="flex flex-1 justify-between text-body-micro text-muted">
{SCALE.map((t) => (
<span key={t}>{t}</span>
))}
</span>
</div>
<ul
aria-label="Availability on Wednesday 14 October"
className="m-0 grid list-none gap-1 p-0"
>
{people.map((p) => (
<li key={p.id}>
<AvailabilityRow
name={p.name}
initials={p.initials}
rangeStart={RANGE_START}
rangeEnd={RANGE_END}
segments={BUSY[p.id] ?? []}
locale="en-GB"
timeZone={ZONE}
/>
</li>
))}
</ul>
</div>
<div className="flex flex-col gap-4 rounded-lg border border-default bg-sunken p-4 @3xl:flex-row @3xl:items-center">
<div className="grid flex-1 gap-2">
<h3 className="text-body-strong text-primary">First shared slot: 16:00 to 17:00</h3>
<div className="flex flex-wrap items-center gap-3">
<AvatarGroup size="md" count={4}>
{free.map((p) => (
<Avatar key={p.id} label={p.name} initials={p.initials} />
))}
</AvatarGroup>
<p className="text-body-small text-secondary">
{free.length} of {people.length} free
{away.length > 0
? ` · busy or away: ${away.map((p) => p.name.split(" ")[0]).join(", ")}`
: ""}
</p>
</div>
</div>
<Button
leadingIcon={<LuCalendarPlus />}
onPress={() => {
setBooked(true);
}}
isDisabled={booked}
className="self-start @3xl:self-center"
>
{booked ? "Invite sent" : "Book this slot"}
</Button>
</div>
</section>
</div>
);
}
More scheduling and calendar blocks
- Month calendarEvents across a month, with a 'more' overflow.
- Week calendarHour-by-hour week view with events.
- Book a meetingPick a day, then a free time slot.
- Event detailsA popover with time, place and people.
- Leave requestRequest time off and see the balance.
- Leave approvalReview a request and approve or decline.
- Shift pickerChoose a morning, evening or night shift.