People nearby
A bottom sheet listing people close to you.
Who’s around
Northwind HQ, Islington · checked in at 09:12
people-nearby.tsx
"use client";
import { useState } from "react";
import { LuUsers } from "react-icons/lu";
import {
Avatar,
BottomSheet,
Button,
ListItem,
MapMarkerPerson,
MapMarkers,
MapView,
Presence,
Segment,
SegmentedControl,
} 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",
},
];
type Density = "comfortable" | "compact";
type Availability = "online" | "away" | "busy";
/** You, checked in at Northwind HQ (a fixed sample position; use the browser's location, with consent, in your app). */
const YOU = [-0.1059, 51.5321] as const;
/** Fixed sample offsets from you in degrees, matching the distances in the list (never live tracking). */
const OFFSETS: (readonly [number, number])[] = [
[0.0001, 0.00005],
[0.0003, -0.0001],
[-0.0002, 0.00025],
[0.0004, 0.0002],
[-0.0005, -0.0002],
[0.0006, -0.0003],
[-0.0007, 0.0004],
[0.0009, 0.0003],
[-0.001, -0.0004],
[0.0011, -0.0005],
[-0.0012, 0.0006],
[0.0014, 0.0005],
];
/** Who is near you at the London office. Distances are illustrative, never live tracking. */
const PEOPLE_NEARBY: {
name: string;
initials: string;
role: string;
where: string;
status: Availability;
}[] = [
...PEOPLE.slice(0, 8).map((p, i) => ({
name: p.name,
initials: p.initials,
role: p.role,
where:
[
"Floor 3, desk 12 · 10 m",
"Café, ground floor · 25 m",
"Floor 3, Thames room · 30 m",
"Floor 2, desk 4 · 40 m",
"Floor 2, desk 9 · 45 m",
"Floor 1, People hub · 60 m",
"Floor 4, Finance · 70 m",
"Reception · 85 m",
][i] ?? "",
status:
(["online", "online", "busy", "away", "online", "busy", "online", "away"] as const)[i] ??
"online",
})),
{
name: "Noah Williams",
initials: "NW",
role: "Office manager",
where: "Reception · 90 m",
status: "online",
},
{
name: "Priya Nair",
initials: "PN",
role: "Head of people",
where: "Floor 1, Garden room · 95 m",
status: "busy",
},
{
name: "Daniel Weiss",
initials: "DW",
role: "Chief technology officer",
where: "Floor 4, desk 1 · 110 m",
status: "away",
},
{
name: "Grace Lee",
initials: "GL",
role: "Customer success manager",
where: "Floor 2, Lark room · 120 m",
status: "online",
},
];
const NEARBY = PEOPLE_NEARBY.map((p, i) => {
const [dLng, dLat] = OFFSETS[i] ?? [0, 0];
return { ...p, lngLat: [YOU[0] + dLng, YOU[1] + dLat] as const };
});
const STATUS = { online: "Available", away: "Away", busy: "Busy" } as const;
/** People nearby: a map of where you are, with a bottom sheet listing the people close to you. */
export default function PeopleNearby() {
const [density, setDensity] = useState<Density>("comfortable");
const compact = density === "compact";
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<section aria-labelledby="nearby-title" className="mx-auto grid w-full max-w-md gap-4">
<header className="grid gap-1">
<h2 id="nearby-title" className="text-heading-h3 text-primary">
Who’s around
</h2>
<p className="text-body-default text-secondary">
Northwind HQ, Islington · checked in at 09:12
</p>
</header>
<MapView
aria-label="Map of the people around you"
defaultCenter={YOU}
defaultZoom={17}
locate
userLocation={YOU}
height={288}
>
<MapMarkers
items={NEARBY}
getId={(p) => p.name}
getLngLat={(p) => p.lngLat}
clusterLabel={(n) => `${String(n)} people`}
renderMarker={(p) => (
<MapMarkerPerson name={p.name} initials={p.initials} workStatus="working" />
)}
/>
</MapView>
<BottomSheet
title={`${String(NEARBY.length)} people nearby`}
trigger={
<Button size="lg" leadingIcon={<LuUsers />} className="w-full">
See {NEARBY.length} people nearby
</Button>
}
footer={
<Button size="lg" variant="secondary" slot="close">
Done
</Button>
}
>
<div className="grid gap-3">
<SegmentedControl
aria-label="List density"
selectedKeys={[density]}
onSelectionChange={(keys) => {
const [next] = [...keys];
if (next === "comfortable" || next === "compact") setDensity(next);
}}
>
<Segment id="comfortable" label="Comfortable" />
<Segment id="compact" label="Compact" />
</SegmentedControl>
<ul className={`m-0 grid list-none p-0 ${compact ? "" : "gap-1"}`}>
{NEARBY.map((p) => (
<li key={p.name}>
<ListItem
href={`#people/${p.initials.toLowerCase()}`}
leading={
<Avatar
size={compact ? "md" : "lg"}
initials={p.initials}
isDecorative
presence={<Presence status={p.status} />}
/>
}
title={p.name}
subtitle={
compact
? `${STATUS[p.status]} · ${p.where}`
: `${p.role} · ${STATUS[p.status]} · ${p.where}`
}
/>
</li>
))}
</ul>
</div>
</BottomSheet>
</section>
</div>
);
}