Share Dialog
Share one item with people and teams, set who in its space can open it, and publish it to the web from its own tab.
- Status
- Since
0.25.11- Accessibility pattern
- APG dialog (modal) with tabs; confirmed stop
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Share Dialog before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify share-dialogInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/share-dialogRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/alert-dialog, @vegastack/button, @vegastack/checkbox, @vegastack/collapsible, @vegastack/copy-button, @vegastack/icon-glyph, @vegastack/input-group, @vegastack/member-list, @vegastack/people-picker, @vegastack/permission-menu, @vegastack/person-avatar, @vegastack/relative-time, @vegastack/responsive-dialog, @vegastack/select, @vegastack/space-picker, @vegastack/switch, @vegastack/tabs, @vegastack/textarea, @vegastack/tooltip, @vegastack/disclosure-trigger.
Usage
import { ShareDialog } from "@/components/ui/share-dialog";
<ShareDialog
trigger={<Button variant="outline">Share</Button>}
levels={LEVELS}
people={entries}
search={searchDirectory}
onInvite={invite}
onLevelChange={setLevel}
onRemove={removeAccess}
generalAccess={general}
onGeneralAccessChange={setGeneral}
publicLink={publicLink}
onCreatePublicLink={publish}
onStopPublicLink={unpublish}
linkUrl={itemUrl}
/>;ShareDialog is data-agnostic: every row, level and link comes in through props and every change
goes out through a callback, so one dialog serves tasks, meetings and pages. It is a
ResponsiveDialog — a md (512px) Dialog from 768px up and a
bottom sheet below it — titled "Share", with two line tabs. Share opens with the "Who can see
this" summary (when the host passes audience), then the invite row and People with access — a
MemberList: PeoplePicker
with one PermissionMenu level for the batch, then a 36px row
per person or team with its level chip and Remove access in the row's ⋯ menu — then Space
access, with Copy link for the item's own link in the footer. Publish holds a
"Publish to the web" switch, off by default — the only control that calls onCreatePublicLink;
copying never publishes. Both panels share one grid cell, so switching tabs never resizes the
dialog. Every string is in labels.
The share-01 block is this dialog wired to sample state, with a page to
try it on.
Scope
| Need | Use |
|---|---|
| Share one record, page or meeting | ShareDialog |
| A space's or team's members, with roles and Remove | MemberList |
| Show who can open an item, without changing it | AccessChip |
| Pick the access level for one person outside a share | PermissionMenu |
Examples
Permissions
canManage gates inviting, changing levels and space access. canPublish gates the Publish tab's
controls — the switch, Reset, Expires and Stop publishing — and follows canManage when unset. A
viewer who cannot publish sees the public link to copy while it is on, and "Not published"
otherwise.
Reset and Stop publishing both ask first, since either stops the live link at once. Pass
publicLink.expiresAt (the instant it lapses) and the Publish tab says when — "Until Oct 22 · 2:30
PM" under Expires — instead of only the preset that was chosen.
Space access's mode is a quiet pill — no border, a tint and a ▾ on hover, like a
RecordChip's ghost form. Leave generalAccess.level out when a
space's members open the item with their own role there: no level shows beside the mode. While
everyone in a named space can open the item, the footer says so ("Members of Sales and people with
access can open it", copyLinkHintSpace).
Hints
hints places a help node — usually an InfoHint — right after the
People with access heading (people), the Space access heading (general) and the Publish switch's
label (publish). The switch keeps its accessible name from the label alone.
What the link exposes, and who can see it
publishExposure replaces the Publish switch's one-line hint with the host's own sentence about
what the link exposes — "Anyone with the link can view this page and 12 pages inside, without
signing in." — and wraps rather than truncates. onPublishRequest is asked before the switch turns
publishing on: confirm what it will expose in an AlertDialog ("Publish 13 pages?") and resolve
false to leave it off. audience adds the "Who can see this" summary at the top of the Share tab,
closed at first; its heading takes audienceCount once known, and onAudienceOpenChange tells the
host when to load its rows. publicLinkOptions adds the host's own settings for a published link
as rows of the Publish tab's settings grid under Expires — each a label then its control, two
children in a fragment, so they line up — what the link shows, say ("Link shows: Latest version |
Approved version"), for a viewer who can publish. While published, the Publish tab's footer says
what stays private and holds Stop publishing, in the same box as the Share tab's footer, so
switching tabs never resizes the dialog.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
levels* | readonly PermissionMenuOption<string>[] | — | The access levels, most to least, e.g. Full access / Can edit / Can view. |
people* | readonly ShareEntry[] | — | The rows of "People with access". |
audience | React.ReactNode | — | "Who can see this": the summary at the top of the Share tab, a collapsible closed at first —
load its rows in onAudienceOpenChange. Its heading is labels.audienceHeading(audienceCount). |
audienceCount | number | — | The head count in the "Who can see this" heading, once known. |
canManage | boolean | true | The viewer may change access: invite, change levels and general access — and, unless
canPublish says otherwise, the public link. |
canPublish | boolean | canManage | The viewer may publish: turn the public link on, reset it, change its expiry or stop it. A viewer who cannot sees the link to copy while it is published, and "Not published" otherwise. |
defaultInvitees | PeoplePickerOption[] | [] | People and teams already chosen to invite — the dialog opens in invite mode. Reactive: when it changes (compared by id), the chips are replaced with it; edits made since the last change stay until then. |
defaultInviteLevel | string | the last of `levels` | The level an invite starts at. |
defaultOpen | boolean | false | Uncontrolled initial open state. |
defaultTab | "publish" | "share" | "share" | The tab the dialog opens on — publish when opened from a "Published" indicator. |
footerNote | React.ReactNode | — | A muted note above the footer, such as "Admins can view this space." |
generalAccess | ShareGeneralAccess | — | The item's space and who in it can open the item. Omit to hide General access. |
generalLevelReadOnly | boolean | false | General access's level shows as text even for a manager, who can still change the mode. |
generalLevels | readonly PermissionMenuOption<string>[] | levels | The levels a whole space may get. |
hints | { people?: React.ReactNode; general?: React.ReactNode; publish?: React.ReactNode; } | {} | Optional help nodes (an info button or a "Learn more" link) placed right after the People with access heading, the Space access heading and the Publish switch's label. |
labels | Partial<ShareDialogLabels> | {} | Override any rendered string. |
linkUrl | string | — | The item's own link, for Copy link in the footer. Omit to hide Copy link. |
loading | boolean | false | People with access is still loading; skeleton rows show. |
onAudienceOpenChange | ((open: boolean) => void) | — | Called when "Who can see this" opens or closes — load its rows on first open. |
onCreatePublicLink | (() => Promise<string | void> | string | void) | — | Turns the public link on — called ONLY by the Publish tab's switch, never by a Copy button. A returned URL is ignored (nothing is copied as a side effect). |
onGeneralAccessChange | ((next: { mode: "space" | "invited"; level: string; }) => void) | — | Changes General access. |
onInvite | ((invite: ShareInvite) => void | boolean | Promise<void | boolean>) | — | Sends an invite; the chips clear when it resolves. Resolve false (or reject) to keep them
for a retry — the host shows its own error. |
onLevelChange | ((id: string, level: string) => void) | — | Changes one row's level. |
onOpenChange | ((open: boolean) => void) | — | Called when the dialog opens or closes. |
onPublicLinkExpiresChange | ((expires: ShareLinkExpiry) => void) | — | Changes when the public link expires. |
onPublishRequest | (() => boolean | Promise<boolean>) | — | Asked before the Publish switch turns the link on — confirm what it will expose ("Publish 13
pages?") and resolve false to leave it off. Without it, the switch publishes at once. |
onRemove | ((id: string) => void) | — | Removes one row's access; adds "Remove access" to its menu. |
onResetPublicLink | (() => void | Promise<void>) | — | Replaces the public link with a new one. |
onStopPublicLink | (() => void | Promise<void>) | — | Turns the public link off. |
open | boolean | — | Controlled open state. |
publicLink | SharePublicLink | null | null | The public link while it is on; null while it is off. |
publicLinkAvailable | boolean | true | Offer the public link row at all. |
publicLinkOptions | React.ReactNode | — | More settings for the public link while it is on (what the link shows, say), for a viewer who can publish: rows of the Publish tab's settings grid under Expires, each a label then its control (two children, in a fragment), so every label and control lines up. |
publicLinkPending | boolean | false | A public-link change is in flight; its controls wait. |
publishExposure | React.ReactNode | — | What the public link exposes, under the Publish switch — "Anyone with the link can view this
page and 12 pages inside, without signing in." Replaces labels.publicHint (on) and
labels.publishHintOff (off) for a viewer who can publish, and wraps rather than truncates. |
search | PeoplePickerSearch | — | Finds people and teams to invite — PeoplePicker's search. Omit to hide the invite row. |
trigger | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | — | The element that opens the dialog, such as a Share button. |
Data attributes and CSS variables on ShareDialog
| Attribute | Values |
|---|---|
data-hidden | "" |
data-share-dialog | "" |
data-slot | "share-audience" | "share-audience-content" | "share-copy-link" | "share-dialog-body" | "share-general" | "share-invite" | "share-invite-row" | "share-panels" | "share-people" | "share-public-link" | "share-public-link-expires-at" | "share-public-link-options" | "share-publish-exposure" | "share-publish-footer" | "share-published-dot" | "share-published-notice" |
Accessibility
- The dialog is named by its "Share" title; the tabs are a
tablistand only the active panel is in the tab order. - Every level menu is named for its row ("Anand Iyer's access: Can view"), and so is every ⋯ menu ("More for Anand Iyer"); a built-in row is locked, still a tab stop, with its reason in a tooltip, and has no ⋯ menu.
- Turning publishing off, by the switch or Stop publishing, asks for confirmation in an
AlertDialogfirst; turning it on asksonPublishRequestwhen the host passes one. - "Who can see this" is a disclosure button (
aria-expanded) whose name carries the head count.
| Key | Action |
|---|---|
| Tab | Moves through the active tab's controls |
| ArrowLeft / ArrowRight | Moves between the Share and Publish tabs |
| Enter | Chooses a person in the invite search |
| Space | Toggles Publish to the web or Notify people |
| Escape | Closes the open menu, then the dialog |
| Contract | States tested |
|---|---|
| Behaviour | default, invite, publish, public-link-on, stop-publishing-confirm, read-only, can-publish, hints, my-space, hidden-space, general-level-read-only, loading, sheet |
| Accessibility | labeled, keyboard, browser-accessibility-test |
| Visual | default, hover, focus, open |