KQ-1679 · SHARE/PERIMETER

Share Perimeter

A share dialog that draws who can open the document: the people with access stand together in a field and a perimeter ring runs around them, its reach set by link access. Widening access springs the ring outward on springs.snap while the old perimeter fades as a ghost on the exit ease, and every colleague dot lights the moment the ring passes it; narrowing pulls it back on the same spring, and new invitees join the cluster on snap as it re-packs on glide. It is a modal dialog with a focus trap: Escape closes, the add field is a combobox over the directory, roles are menus moved with the arrow keys and link access is a radio group.

KQ-1679 · SHARE/PERIMETER
Coldbrook / PlanningQ4 river crossings plan

Owner Mara Lindqvist · invited only

Crossings to rebuild before the spring flows

Three footbridges and the ford at Hollins Weir need survey work before the river rises. The order below follows risk, then access for the crane.

  1. Weir footbridge — deck survey, 3 Nov
  2. Mill Lane ford — scour check after the first flood
  3. Ash Cut bridge — bearings and the east abutment

invited only · 5 can open it

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/share-perimeter

Props

people / defaultPeople
SharePerson[]
default — / defaultSharePeople
Everyone with access: { id, name, email, role, status?: "active" | "pending", invitedAt? }, controlled or initial.
onPeopleChange
(people) => void
default —
Fires from the invite, role change or removal that changed the list.
directory
ShareContact[]
default defaultShareDirectory
Colleagues the add field suggests as you type: { id, name, email }.
link / defaultLink
"invited" | "org" | "anyone"
default — / "invited"
Link access, controlled or initial; it sets the perimeter's reach.
onLinkChange
(link) => void
default —
Fires from the press or arrow key that changed link access.
onInvite
(person, role) => void | Promise<void>
default —
Someone was invited. Return a promise to show it sending; a rejection's message stays inline on the row.
onRoleChange
(person, role) => void
default —
A role was changed from a person's menu.
onResend
(person) => void | Promise<void>
default —
Resend was pressed on a pending invite.
onRemove
(person) => void
default —
Remove access was chosen from a person's menu.
open / defaultOpen
boolean
default — / true
Whether the dialog is open over the document, controlled or initial.
onOpenChange
(open) => void
default —
Share, Done, the close button, the scrim or Escape opened or closed it.
documentTitle
string
default "Q4 river crossings plan"
The document being shared.
org
{ name, domain, size }
default Coldbrook, coldbrook.co, 240
The organisation behind the middle level; addresses at its domain are colleagues.
me
string
default "mara"
The id of the person looking, marked "you".
now
number
default defaultShareNow
The moment invite ages count from, in ms; never read from the clock.
status
"ready" | "loading" | "error"
default "ready"
Loading shows placeholder rows; error explains and offers Try again.
onRetry
() => void
default —
Try again was pressed after the access list failed to load.
ringStyle
"line" | "dash" | "halo"
default "line"
The perimeter as a crisp line, a dashed fence that opens up as access widens, or a soft halo.
density
"compact" | "cozy" | "roomy"
default "cozy"
Row heights, avatar size, the field's height and padding.
accent
string
default "var(--accent-bright)"
The perimeter, lit colleagues, the chosen level and Done; text on it flips light or dark with its lightness.
height
number
default 540
Height of the frame in px; the people list scrolls inside the dialog.
disabled
boolean
default false
Nothing can be changed; the dialog still opens and closes.
className
string
default —
Classes for the frame.