Event details
A popover with time, place and people.
Thursday 15 October
Your agenda. Select an event to see where it is and who is coming.
- 10:30 to 11:15YTARZSand 1 more
- 11:00 to 11:30SMAK
- 13:30 to 15:00SM
- 15:00 to 16:00MJCMOHand 3 more
event-details.tsx
"use client";
import { useState } from "react";
import { LuEllipsis, LuPencil, LuTrash2 } from "react-icons/lu";
import {
Avatar,
AvatarGroup,
Button,
CalendarEvent,
EventPopover,
IconButton,
type EventPopoverGuest,
} from "@nexera-ui/react";
const ZONE = "Asia/Karachi";
const at = (hm: string) => new Date(`2026-10-15T${hm}:00+05:00`);
interface AgendaItem {
id: string;
title: string;
from: string;
to: string;
type: "timed" | "tentative" | "focus-time";
location: string;
video: boolean;
guests: EventPopoverGuest[];
}
const AGENDA: AgendaItem[] = [
{
id: "zara",
title: "Interview: Zara Siddiqui",
from: "10:30",
to: "11:15",
type: "timed",
location: "Lahore HQ, Room Margalla (8 seats)",
video: true,
guests: [
{ name: "Yuki Tanaka", initials: "YT", role: "Organizer", response: "yes" },
{ name: "Ahmed Raza", initials: "AR", role: "Interviewer", response: "yes" },
{ name: "Zara Siddiqui", initials: "ZS", role: "Candidate", response: "awaiting" },
{ name: "Luis Ortega", initials: "LO", role: "Optional", response: "no" },
],
},
{
id: "one-to-one",
title: "1:1 Sara and Ayesha",
from: "11:00",
to: "11:30",
type: "timed",
location: "Video call",
video: true,
guests: [
{ name: "Sara Malik", initials: "SM", role: "Organizer", response: "yes" },
{ name: "Ayesha Khan", initials: "AK", response: "yes" },
],
},
{
id: "q4",
title: "Write Q4 people plan",
from: "13:30",
to: "15:00",
type: "focus-time",
location: "No location",
video: false,
guests: [{ name: "Sara Malik", initials: "SM", role: "Organizer", response: "yes" }],
},
{
id: "townhall",
title: "Quarterly town hall",
from: "15:00",
to: "16:00",
type: "tentative",
location: "London office, Atrium and live stream",
video: true,
guests: [
{ name: "Mia Jensen", initials: "MJ", role: "Organizer", response: "yes" },
{ name: "Chloe Martin", initials: "CM", response: "yes" },
{ name: "Omar Haddad", initials: "OH", response: "awaiting" },
{ name: "Ayesha Khan", initials: "AK", response: "yes" },
{ name: "Luis Ortega", initials: "LO", response: "no" },
{ name: "Yuki Tanaka", initials: "YT", response: "yes" },
],
},
];
const summary = (guests: EventPopoverGuest[]) => {
const count = (r: EventPopoverGuest["response"]) => guests.filter((g) => g.response === r).length;
return `${guests.length} ${guests.length === 1 ? "guest" : "guests"} · ${count("yes")} yes, ${count("awaiting")} awaiting, ${count("no")} no`;
};
/** Event details: today's agenda; each event opens a popover with time, place, guests, a join button and RSVP. */
export default function EventDetails() {
const [answers, setAnswers] = useState<Record<string, string>>({ zara: "yes" });
const [removed, setRemoved] = useState<string[]>([]);
return (
<div className="@container bg-page px-4 py-8 @3xl:px-8">
<section
aria-labelledby="agenda-title"
className="mx-auto grid w-full max-w-2xl gap-4 rounded-xl border border-default bg-surface p-4 shadow-sm @3xl:p-6"
>
<header className="grid gap-1">
<h2 id="agenda-title" className="text-heading-h3 text-primary">
Thursday 15 October
</h2>
<p className="text-body-default text-secondary">
Your agenda. Select an event to see where it is and who is coming.
</p>
</header>
<ul className="m-0 grid list-none gap-3 p-0">
{AGENDA.filter((e) => !removed.includes(e.id)).map((e) => (
<li
key={e.id}
className="flex flex-col gap-2 @3xl:flex-row @3xl:items-center @3xl:gap-4"
>
<span className="w-28 shrink-0 text-body-small text-secondary">
{e.from} to {e.to}
</span>
<div className="min-w-0 flex-1">
<EventPopover
title={e.title}
when={`Thursday 15 October · ${e.from} to ${e.to}`}
meta="Does not repeat · Pakistan Standard Time"
calendarTone={e.type === "focus-time" ? "success" : "info"}
location={e.location}
guests={{ summary: summary(e.guests), items: e.guests }}
placement="bottom start"
actions={
<>
<IconButton
variant="ghost"
size="sm"
aria-label="Edit event"
icon={<LuPencil />}
/>
<IconButton
variant="ghost"
size="sm"
aria-label="Delete event"
icon={<LuTrash2 />}
slot="close"
onPress={() => {
setRemoved((r) => [...r, e.id]);
}}
/>
<IconButton
variant="ghost"
size="sm"
aria-label="More actions"
icon={<LuEllipsis />}
/>
</>
}
{...(e.video ? { join: <Button size="sm">Join with video</Button> } : {})}
{...(e.guests.length > 1
? {
rsvp: {
label: "Going?",
value: answers[e.id] ?? null,
onChange: (value: string) => {
setAnswers((a) => ({ ...a, [e.id]: value }));
},
options: [
{ value: "yes", label: "Yes" },
{ value: "maybe", label: "Maybe" },
{ value: "no", label: "No" },
],
},
}
: {})}
>
<CalendarEvent
title={e.title}
type={e.type}
layout="block"
start={at(e.from)}
end={at(e.to)}
timeZone={ZONE}
className="w-full"
/>
</EventPopover>
</div>
<AvatarGroup size="sm" count={3}>
{e.guests.map((g) => (
<Avatar key={g.name} label={g.name} initials={g.initials ?? ""} />
))}
</AvatarGroup>
</li>
))}
</ul>
{removed.length > 0 ? (
<Button
variant="ghost"
className="justify-self-start"
onPress={() => {
setRemoved([]);
}}
>
Restore deleted events
</Button>
) : null}
</section>
</div>
);
}
More scheduling and calendar blocks
- Month calendarEvents across a month, with a 'more' overflow.
- Week calendarHour-by-hour week view with events.
- Team availabilityWho is free when, side by side.
- Book a meetingPick a day, then a free time slot.
- Leave requestRequest time off and see the balance.
- Leave approvalReview a request and approve or decline.
- Shift pickerChoose a morning, evening or night shift.