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.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-record-sharingpnpm add react-native-gesture-handler react-native-reanimated react-native-svgUsage
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
onSearchafter 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.onSearchmay return an array or a promise. - Results already shared are hidden; when
resourcesis 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 rejectedonAddkeeps 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]). RecordSharingPanelrenders without card chrome (like web, where it lives in a popover) — wrap it in your own card when used inline.
API Reference
RecordSharingPanelProps
| Prop | Type | Default | Description |
|---|---|---|---|
sharedEntities | SharedEntity[] | - | Entities the record is shared with (required) |
resources | SharingResourceType[] | all types | Resource types offered in search results. Web documents ['user', 'team', 'role'] but never applies the filter; native filters only when the prop is set |
permissionPresets | PermissionPresetOption[] | Can view / Can comment / Can edit / Can share / Full access | Permission presets (labels + descriptions) |
defaultPermission | number | 1 (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) |
isLoading | boolean | false | Show skeleton rows instead of the list |
isSearching | boolean | false | Force the search spinner (it also shows while an async onSearch is running) |
isAddPending | boolean | false | Add button pending state |
pendingPermissionChanges | Record<string, boolean> | - | Per-entity permission spinner |
pendingRemovals | Record<string, boolean> | - | Per-entity remove spinner |
title | string | 'Share with' | Header title |
maxHeight | number | 320 | Max height (dp) of the shared-entity list; it scrolls beyond |
className | string | - | Container classes |
RecordSharingButtonProps
Extends RecordSharingPanelProps — every panel prop is forwarded to the panel inside the sheet.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'button' | 'avatar-group' | 'button' | Outline Share button, or overlapping avatars of sharedEntities |
maxAvatars | number | 3 | Avatars shown before the +N chip (avatar-group) |
size | 'sm' | 'default' | 'lg' | 'default' | Trigger size |
open | boolean | - | Controlled sheet visibility |
onOpenChange | (open: boolean) => void | - | Sheet visibility callback |
disabled | boolean | false | Disable the trigger |
triggerClassName | string | - | Trigger classes |
className | string | - | Wrapper View classes (not forwarded to the panel) |
ref | Ref<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
| Prop | Type | Default | Description |
|---|---|---|---|
entity | SharedEntity | - | Row entity |
permissionPresets | PermissionPresetOption[] | - | 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 |
isPermissionChangePending | boolean | false | Spinner in the permission button |
isRemovePending | boolean | false | Spinner in the remove button |
SharedEntity
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier |
name | string | Display name |
description | string | Secondary text |
type | SharingResourceType | 'user' | 'team' | 'role' | 'tenant' | 'public' |
avatarUrl | string | null | User photo (users only) |
initials | string | Fallback initials for users without a photo |
permission | number | Permission bitmask |
SharingSearchResult
Same fields as SharedEntity without permission.
PermissionPresetOption
| Field | Type | Description |
|---|---|---|
label | string | Display label |
value | number | Permission bitmask |
description | string | Shown under the label in the picker sheet |
Permission constants
| Constant | Value |
|---|---|
SharingPermission.READ | 1 |
SharingPermission.WRITE | 2 |
SharingPermission.COMMENT | 4 |
SharingPermission.SHARE | 8 |
SharingPermission.DELETE | 16 |
SharingPermissionPreset.CAN_VIEW | 1 (read) |
SharingPermissionPreset.CAN_COMMENT | 5 (read + comment) |
SharingPermissionPreset.CAN_EDIT | 7 (read + write + comment) |
SharingPermissionPreset.CAN_SHARE | 15 (+ share) |
SharingPermissionPreset.FULL_ACCESS | 31 (all) |
Components
| Component | Description |
|---|---|
RecordSharingButton | Share trigger (button or avatar group) that opens the panel in a bottom sheet |
RecordSharingPanel | Search + staged selection + batch permission + shared-entity list |
RecordSharingItem | One shared entity row with permission picker and remove button |
Also exported: recordSharingButtonVariants (tv() slots base / avatar / avatarText), SharingPermission, SharingPermissionPreset.
Type Exports
| Type | Description |
|---|---|
RecordSharingButtonProps | Button props (panel props + trigger props) |
RecordSharingPanelProps | Panel props |
RecordSharingItemProps | Row props |
SharedEntity | Shared entity with permission |
SharingSearchResult | Search result |
SharingResourceType | 'user' | 'team' | 'role' | 'tenant' | 'public' |
SharingPermissionValue | Union of SharingPermission values |
SharingPermissionPresetValue | Union of SharingPermissionPreset values |
PermissionPresetOption | Permission 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-sharingis unchanged. onAdd(result, permission)→onAdd(entities: Array<{ id, type, permission }>)— results are staged and shared in one batch with a single permission.onSearchis debounced (300 ms) instead of firing on every keystroke; it may now also return an array synchronously.titledefault'Share'→'Share with'(web).RecordSharingPanelno longer draws its own card (border / rounded / padding) — wrap it when used inline.RecordSharingButtonhosts it in a bottom sheet.resourcesis now applied as a search-result filter when set (it was ignored).- Default presets now carry translated labels / descriptions through
useUiTranslation(); a custom preset withoutdescriptionshows no subtitle in the picker.
Record PDF Export
Three-step wizard (Select a Template → Preview → PDF) that renders a Docyrus record to PDF with a saved HTML template, previews the server-compiled document in a WebView, and shares or prints the result. Includes the headless useDocyrusRecordPdfExport hook.
ResourceSchedulerPanel
Preset-driven resource timeline — grouped multi-unit headers, a sticky resource column with synced scrolling, long-press drag to move events across time and resources, edge resize, and event detail bottom sheets.