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