Skip to content
Component installs need the registry setup
VegaStack Design

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
stable
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-dialog

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/share-dialog

Run 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

NeedUse
Share one record, page or meetingShareDialog
A space's or team's members, with roles and RemoveMemberList
Show who can open an item, without changing itAccessChip
Pick the access level for one person outside a sharePermissionMenu

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.

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

PropTypeDefaultDescription
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".
audienceReact.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).
audienceCountnumber—The head count in the "Who can see this" heading, once known.
canManagebooleantrueThe viewer may change access: invite, change levels and general access — and, unless canPublish says otherwise, the public link.
canPublishbooleancanManageThe 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.
defaultInviteesPeoplePickerOption[][]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.
defaultInviteLevelstringthe last of `levels`The level an invite starts at.
defaultOpenbooleanfalseUncontrolled initial open state.
defaultTab"publish" | "share""share"The tab the dialog opens on — publish when opened from a "Published" indicator.
footerNoteReact.ReactNode—A muted note above the footer, such as "Admins can view this space."
generalAccessShareGeneralAccess—The item's space and who in it can open the item. Omit to hide General access.
generalLevelReadOnlybooleanfalseGeneral access's level shows as text even for a manager, who can still change the mode.
generalLevelsreadonly PermissionMenuOption<string>[]levelsThe 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.
labelsPartial<ShareDialogLabels>{}Override any rendered string.
linkUrlstring—The item's own link, for Copy link in the footer. Omit to hide Copy link.
loadingbooleanfalsePeople 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.
openboolean—Controlled open state.
publicLinkSharePublicLink | nullnullThe public link while it is on; null while it is off.
publicLinkAvailablebooleantrueOffer the public link row at all.
publicLinkOptionsReact.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.
publicLinkPendingbooleanfalseA public-link change is in flight; its controls wait.
publishExposureReact.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.
searchPeoplePickerSearch—Finds people and teams to invite — PeoplePicker's search. Omit to hide the invite row.
triggerReact.ReactElement<unknown, string | React.JSXElementConstructor<any>>—The element that opens the dialog, such as a Share button.

Data attributes and CSS variables on ShareDialog

AttributeValues
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 tablist and 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 AlertDialog first; turning it on asks onPublishRequest when the host passes one.
  • "Who can see this" is a disclosure button (aria-expanded) whose name carries the head count.
KeyAction
TabMoves through the active tab's controls
ArrowLeft / ArrowRightMoves between the Share and Publish tabs
EnterChooses a person in the invite search
SpaceToggles Publish to the web or Notify people
EscapeCloses the open menu, then the dialog
ContractStates tested
Behaviourdefault, invite, publish, public-link-on, stop-publishing-confirm, read-only, can-publish, hints, my-space, hidden-space, general-level-read-only, loading, sheet
Accessibilitylabeled, keyboard, browser-accessibility-test
Visualdefault, hover, focus, open

Do / Don't

Do
Compute each row's reason and readOnly on the server from how the person has access, and pass canPublish from the same rules.
Don't
Let the dialog decide access — it only shows the rows and reports changes.

On this page