Skip to content
kerfjsGitHub

EventPopover

A small card with an event's details, hung from wherever it was opened.

SourcePackage @kerf-js/veneer

Preview

tsx
import { state } from '@kerf-js/core'import {  Button,  EventPopover,  Stack,  Text} from '@kerf-js/veneer'import type { CalEvent } from '@kerf-js/veneer-core'export function Details() {  const open = state(false)  const said = state(    'Open the event, then join, edit or delete it.',  )  let opener: HTMLElement | undefined  return (    <Stack      gap="related"      align="center"    >      <Button        ref={(n: HTMLElement) => (opener = n)}        emphasis="outline"        tone="neutral"        onClick={() => (open.value = !open.value)}      >        {REVIEW.title}      </Button>      <Text        role="caption"        subtle      >        {said.value}      </Text>      <EventPopover        event={REVIEW}        open={open}        anchor={() => opener}        side="bottom"        align="center"        zoneLabel="Stockholm (CET)"        locale="en-GB"        hourCycle="h23"        onJoin={() => (said.value = 'Joining the call')}        onEdit={() => (said.value = 'Editing')}        onDelete={() => (said.value = 'Deleted')}      />    </Stack>  )}const TEAM_EVENTS: CalEvent[] = [  {    id: 'e1',    title: 'Platform standup',    start: '2026-11-02T09:00',    end: '2026-11-02T09:15',    cal: 1,    recurrence: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR',    location: 'Room Atlas',    attendees: ['Mara Lindqvist', 'Jun Tanaka'],  },  {    id: 'e2',    title: 'Platform standup',    start: '2026-11-03T09:00',    end: '2026-11-03T09:15',    cal: 1,    recurrence: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR',    location: 'Room Atlas',    attendees: ['Mara Lindqvist', 'Jun Tanaka'],  },  {    id: 'e3',    title: 'Checkout review',    start: '2026-11-03T10:00',    end: '2026-11-03T11:00',    cal: 2,    video: true,    attendees: [      'Priya Raman',      'Tomás Oliveira',      'Mara Lindqvist',    ],  },  {    id: 'e4',    title: 'Incident review: mobile-bff',    start: '2026-11-03T13:00',    end: '2026-11-03T13:45',    cal: 4,    video: true,    location: 'Room Birch',    attendees: ['Sam Okafor', 'Inés Vidal', 'Jun Tanaka'],  },  {    id: 'e5',    title: 'Ranking v2 handover',    start: '2026-11-03T15:00',    end: '2026-11-03T16:00',    cal: 3,    location: 'Room Cedar',    attendees: ['Lena Fischer', 'Noor Haddad'],  },  {    id: 'e6',    title: 'Priya out of office',    start: '2026-11-03',    end: '2026-11-03',    allDay: true,    cal: 6,  },  {    id: 'e7',    title: 'Release 2026.11',    start: '2026-11-04T11:00',    end: '2026-11-04T12:00',    cal: 5,    attendees: [      'Mara Lindqvist',      'Jun Tanaka',      'Tomás Oliveira',    ],  },  {    id: 'e8',    title: 'Design crit',    start: '2026-11-04T14:00',    end: '2026-11-04T15:00',    cal: 2,    tentative: true,    video: true,  },  {    id: 'e9',    title: 'Cost review',    start: '2026-11-05T10:00',    end: '2026-11-05T11:00',    cal: 1,    recurrence: 'FREQ=MONTHLY',    attendees: ['Mara Lindqvist', 'Lena Fischer'],  },  {    id: 'e10',    title: 'On-call handover',    start: '2026-11-06T16:00',    end: '2026-11-06T16:30',    cal: 1,    recurrence: 'FREQ=WEEKLY',    video: true,  },]const REVIEW = TEAM_EVENTS.find((e) => e.id === 'e4')!

Installation

npm install @kerf-js/veneer

Usage

tsx
import { EventPopover } from '@kerf-js/veneer'

Accessibility

Escape
Closes and returns to the event
  • It is a non-modal dialog, so the rest of the page stays usable.

API Reference

PropTypeDefaultDescription
eventCalEvent | null—The event to show.
openState<boolean>—A state you own.
anchor() => HTMLElement—Returns the element it hangs from. Focus goes back there.
zoneLabelstring—Such as "Stockholm (CET)".
localestring—The language and region used to format values.
hourCyclestring—h12 for AM and PM, h23 for a 24-hour clock. The locale's otherwise.
sideSide—Which edge it sits on.
alignAlign—How it lines up with what opened it.
EventTypeDefaultDescription
onEdit((e: CalEvent) => void)—Adds Edit.
onDelete((e: CalEvent) => void)—Adds Delete.
onJoin((e: CalEvent) => void)—Join was pressed.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="popover"] [data-part="content"]

contenttitleactions

RelatedEvent editorPopover