Docyrus

RecordSharing

Share a record with users, teams, roles or the workspace — staged multi-select search, one batch permission, per-row pending states and a share trigger that opens the panel in a bottom sheet.

iOSAndroid
Preview RecordSharing on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-record-sharing
Required Packages(3 packages)
pnpm add react-native-gesture-handler react-native-reanimated react-native-svg

Usage

Share button (bottom sheet)

import {
  RecordSharingButton,
  SharingPermissionPreset,
  type SharingSearchResult
} from '@/components/docyrus-native/record-sharing';

<RecordSharingButton
  variant="avatar-group"
  maxAvatars={3}
  sharedEntities={shares}
  onSearch={async (query): Promise<SharingSearchResult[]> => searchPrincipals(query)}
  onAdd={entities => addShares.mutateAsync(entities)}
  onPermissionChange={(id, permission) => updateShare.mutateAsync({ id, permission })}
  onRemove={id => removeShare.mutateAsync(id)}
  isAddPending={addShares.isPending}
  pendingPermissionChanges={pendingPermissionChanges}
  pendingRemovals={pendingRemovals}
  defaultPermission={SharingPermissionPreset.CAN_VIEW} />

Inline panel

import { RecordSharingPanel } from '@/components/docyrus-native/record-sharing';

<View className="rounded-lg border border-border bg-card p-4">
  <RecordSharingPanel
    sharedEntities={shares}
    resources={['user', 'team']}
    onSearch={searchPrincipals}
    onAdd={entities => addShares(entities)}
    onPermissionChange={(id, permission) => updateShare(id, permission)}
    onRemove={id => removeShare(id)} />
</View>

Behaviour:

  • Typing runs onSearch after a 300 ms debounce once the query has at least 2 characters (native extra). Only the latest request may write results, so slow responses never overwrite newer ones. onSearch may return an array or a promise.
  • Results already shared are hidden; when resources is set, results of other types are hidden too.
  • Tapping results stages them as chips. One permission (preset picker sheet with descriptions) applies to the whole batch, and Add calls onAdd(entities[]) once. A rejected onAdd keeps the selection.
  • Shared entities are sorted public → workspace → role → team → user. Each row has a permission picker and a remove button with its own spinner (pendingPermissionChanges[id], pendingRemovals[id]).
  • RecordSharingPanel renders without card chrome (like web, where it lives in a popover) — wrap it in your own card when used inline.

API Reference

RecordSharingPanelProps

PropTypeDefaultDescription
sharedEntitiesSharedEntity[]-Entities the record is shared with (required)
resourcesSharingResourceType[]all typesResource types offered in search results. Web documents ['user', 'team', 'role'] but never applies the filter; native filters only when the prop is set
permissionPresetsPermissionPresetOption[]Can view / Can comment / Can edit / Can share / Full accessPermission presets (labels + descriptions)
defaultPermissionnumber1 (CAN_VIEW)Initial batch permission for new shares
onSearch(query: string) => Promise<SharingSearchResult[]> | SharingSearchResult[]-Search callback (required)
onAdd(entities: Array<{ id: string; type: SharingResourceType; permission: number }>) => void | Promise<void>-Shares the staged selection (required)
onPermissionChange(entityId: string, permission: number) => void | Promise<void>-Changes an entity's permission (required)
onRemove(entityId: string) => void | Promise<void>-Removes access (required)
isLoadingbooleanfalseShow skeleton rows instead of the list
isSearchingbooleanfalseForce the search spinner (it also shows while an async onSearch is running)
isAddPendingbooleanfalseAdd button pending state
pendingPermissionChangesRecord<string, boolean>-Per-entity permission spinner
pendingRemovalsRecord<string, boolean>-Per-entity remove spinner
titlestring'Share with'Header title
maxHeightnumber320Max height (dp) of the shared-entity list; it scrolls beyond
classNamestring-Container classes

RecordSharingButtonProps

Extends RecordSharingPanelProps — every panel prop is forwarded to the panel inside the sheet.

PropTypeDefaultDescription
variant'button' | 'avatar-group''button'Outline Share button, or overlapping avatars of sharedEntities
maxAvatarsnumber3Avatars shown before the +N chip (avatar-group)
size'sm' | 'default' | 'lg''default'Trigger size
openboolean-Controlled sheet visibility
onOpenChange(open: boolean) => void-Sheet visibility callback
disabledbooleanfalseDisable the trigger
triggerClassNamestring-Trigger classes
classNamestring-Wrapper View classes (not forwarded to the panel)
refRef<View>-Wrapper View ref
align'start' | 'center' | 'end'-Web popover prop — not applicable on native (the panel opens in a large-detent bottom sheet)
side'top' | 'right' | 'bottom' | 'left'-Web popover prop — not applicable on native

RecordSharingItemProps

PropTypeDefaultDescription
entitySharedEntity-Row entity
permissionPresetsPermissionPresetOption[]-Presets for the picker sheet
onPermissionChange(entityId: string, permission: number) => void | Promise<void>-Called when another preset is picked
onRemove(entityId: string) => void | Promise<void>-Remove access
isPermissionChangePendingbooleanfalseSpinner in the permission button
isRemovePendingbooleanfalseSpinner in the remove button

SharedEntity

FieldTypeDescription
idstringUnique identifier
namestringDisplay name
descriptionstringSecondary text
typeSharingResourceType'user' | 'team' | 'role' | 'tenant' | 'public'
avatarUrlstring | nullUser photo (users only)
initialsstringFallback initials for users without a photo
permissionnumberPermission bitmask

SharingSearchResult

Same fields as SharedEntity without permission.

PermissionPresetOption

FieldTypeDescription
labelstringDisplay label
valuenumberPermission bitmask
descriptionstringShown under the label in the picker sheet

Permission constants

ConstantValue
SharingPermission.READ1
SharingPermission.WRITE2
SharingPermission.COMMENT4
SharingPermission.SHARE8
SharingPermission.DELETE16
SharingPermissionPreset.CAN_VIEW1 (read)
SharingPermissionPreset.CAN_COMMENT5 (read + comment)
SharingPermissionPreset.CAN_EDIT7 (read + write + comment)
SharingPermissionPreset.CAN_SHARE15 (+ share)
SharingPermissionPreset.FULL_ACCESS31 (all)

Components

ComponentDescription
RecordSharingButtonShare trigger (button or avatar group) that opens the panel in a bottom sheet
RecordSharingPanelSearch + staged selection + batch permission + shared-entity list
RecordSharingItemOne shared entity row with permission picker and remove button

Also exported: recordSharingButtonVariants (tv() slots base / avatar / avatarText), SharingPermission, SharingPermissionPreset.

Type Exports

TypeDescription
RecordSharingButtonPropsButton props (panel props + trigger props)
RecordSharingPanelPropsPanel props
RecordSharingItemPropsRow props
SharedEntityShared entity with permission
SharingSearchResultSearch result
SharingResourceType'user' | 'team' | 'role' | 'tenant' | 'public'
SharingPermissionValueUnion of SharingPermission values
SharingPermissionPresetValueUnion of SharingPermissionPreset values
PermissionPresetOptionPermission preset

i18n keys

Same keys as web via useUiTranslation(): ui.recordSharing.share, shareWith, searchPeoplePlaceholder, add, adding, canView, canComment, canEdit, canShare, fullAccess, team, role, workspace, public, removeAccess, noSharedEntities, ui.common.noUsersFound. Native additions: ui.recordSharing.permission, current, canViewDescription, canCommentDescription, canEditDescription, canShareDescription, fullAccessDescription, ui.common.remove.

Breaking changes

  • The component moved to a folder; import path @/components/docyrus-native/record-sharing is unchanged.
  • onAdd(result, permission) → onAdd(entities: Array<{ id, type, permission }>) — results are staged and shared in one batch with a single permission.
  • onSearch is debounced (300 ms) instead of firing on every keystroke; it may now also return an array synchronously.
  • title default 'Share' → 'Share with' (web).
  • RecordSharingPanel no longer draws its own card (border / rounded / padding) — wrap it when used inline. RecordSharingButton hosts it in a bottom sheet.
  • resources is now applied as a search-result filter when set (it was ignored).
  • Default presets now carry translated labels / descriptions through useUiTranslation(); a custom preset without description shows no subtitle in the picker.

On this page