ContactChannelsField
Backend-agnostic contact points for a record — emails, phones, messaging and social handles with a primary per kind, validation status and per-brand consent.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-contact-channels-fieldThe data model (ContactChannel, the three-level consent cache, validation records, brands) and the pure helpers in lib/contact-channels are the same as web, so a channel list round-trips between platforms with no mapping.
ContactChannelsFieldis a compact summary: the primary channel per kind (with a+Nbadge), a verified tick, a per-brand consent row (Email / Phone / WhatsApp / SMS dots) and Add channel. Details opens a bottom sheet with the full manager, or callsonDetailsClickso you can openContactChannelsPanelinstead.ContactChannelsManageris the full editor. Rows are cards grouped by kind. Tapping a card opens the channel sheet.ChannelDetailSheet(web:ChannelDetailPopover, kept as an alias) shows the attribute editor, validation and the consent editor, plus Make primary and Remove.ConsentMatrixhas one collapsible section per brand (Org-wide first). Each row is a medium × purpose with a three-way control (opted in / unknown / opted out). Transactional is medium-agnostic. Marketing and newsletter are split across the mediums the channel type allows.
Without channelActions, every edit is applied to value and reported through onChange. With channelActions, the field calls your handlers instead (for example against the Docyrus API).
Also available as a form field: DocyFieldContactChannels (@/components/docyrus-native/form-fields/docy-field-contact-channels) stores a ContactChannel[] and edits it inline with the manager.
Usage
import { ContactChannelsField, type ContactChannel } from '@/components/docyrus-native/contact-channels-field';
const [channels, setChannels] = useState<ContactChannel[]>([]);
<ContactChannelsField value={channels} onChange={setChannels} brands={brands} />import { ContactChannelsManager } from '@/components/docyrus-native/contact-channels-field';
<ContactChannelsManager value={channels} onChange={setChannels} groupByKind enableConsent enableValidation />API Reference
ContactChannelsField
| Prop | Type | Default | Description |
|---|---|---|---|
value | ContactChannel[] | - | Controlled channels (required) |
onChange | (next: ContactChannel[]) => void | - | Full next list after an edit (required) |
brands | ContactBrand[] | - | Brands for the consent summary (org-wide row when omitted) |
channelActions | ContactChannelActionHandlers | - | Granular per-channel handlers (onCreate / onSave / onMakePrimary / onRemove / onRecordConsent) |
enableChannelDetails | boolean | true | Per-channel details sheet next to each value |
title | string | 'Contact Points' | Heading |
hideDetails | boolean | - | Hide the Details link |
detailsLabel | string | 'Details' | Details link label |
onDetailsClick | () => void | - | Replaces the built-in manager sheet (e.g. open ContactChannelsPanel) |
showConsent | boolean | true | Show the consent summary |
disabled | boolean | - | Disable editing |
readOnly | boolean | - | Read-only |
emptyText | string | 'No contact points yet.' | Empty placeholder |
className | string | - | Container classes |
ContactChannelActionHandlers
| Handler | Signature | Description |
|---|---|---|
onCreate | (channel: ContactChannel) => void | Promise<unknown> | Persist a new channel (temporary id) |
onSave | (channel: ContactChannel) => void | Promise<unknown> | Persist edited attributes |
onMakePrimary | (channel: ContactChannel) => void | Promise<unknown> | Promote to primary for its kind |
onRemove | (channel: ContactChannel) => void | Promise<unknown> | Remove / archive |
onRecordConsent | (channel, brandKey, medium | null, purpose, status) => void | Promise<unknown> | Record one consent change instead of editing the cache |
ContactChannelsManager
| Prop | Type | Default | Description |
|---|---|---|---|
value | ContactChannel[] | - | Controlled channels (required) |
onChange | (next: ContactChannel[]) => void | - | Full next list on add / edit / remove (required) |
brands | ContactBrand[] | - | Brands for consent scoping |
disabled | boolean | - | Disable every interaction |
readOnly | boolean | - | Hide editors and actions |
enableConsent | boolean | true | Consent section in the channel sheet |
enableValidation | boolean | true | Validation chips / section |
groupByKind | boolean | true | Group under kind headers |
emptyText | string | 'No contact channels yet.' | Empty placeholder |
className | string | - | Container classes |
ChannelEditor
| Prop | Type | Default | Description |
|---|---|---|---|
value | ChannelDraft | - | Draft (required) |
onChange | (next: ChannelDraft) => void | - | Draft change (required) |
disabled | boolean | - | Disable inputs |
showPrimary | boolean | true | Primary switch |
showVerified | boolean | true | Verified switch |
showValidation | boolean | true | Inline value validation |
ChannelDetailSheet / ChannelDetailPopover
| Prop | Type | Default | Description |
|---|---|---|---|
channel | ContactChannel | - | Channel (required) |
brands | ContactBrand[] | - | Brands for the consent editor |
locked | boolean | - | Read-only |
onSave / onMakePrimary / onRemove | (channel: ContactChannel) => void | - | Actions (required) |
onConsentCache | (channel, next: ConsentCache) => void | - | Cache-based consent edit |
onConsentCell | (channel, brandKey, medium, purpose, status) => void | - | Ledger consent edit (wins over onConsentCache) |
trigger | ReactNode | chevron button | Trigger node |
showValidation | boolean | true | Native: validation section |
showConsent | boolean | true | Native: consent section |
ConsentMatrix
| Prop | Type | Default | Description |
|---|---|---|---|
channelType | ChannelType | - | Decides the allowed mediums (required) |
consent | ConsentCache | - | Current cache |
brands | ContactBrand[] | - | Extra brand sections |
onChange | (next: ConsentCache) => void | - | Rebuilt cache |
onCellChange | (brandKey, medium | null, purpose, status) => void | - | Single ledger entry |
disabled | boolean | - | Read-only |
className | string | - | Container classes |
Components
| Component | Description |
|---|---|
ContactChannelsField | Summary + Details sheet |
ContactChannelsManager | Full editor |
ChannelDetailSheet (ChannelDetailPopover) | Per-channel sheet |
ChannelEditor | Attribute editor |
ConsentMatrix | Consent editor |
ValidationStatusBadge / ConsentStatusBadge / VerifiedBadge / ConsentStatusIcon | Status visuals |
AddChannelSheet / ContactChannelFormSheet | Native: add button + sheet, shared sheet shell |
Helpers
CHANNEL_TYPES, CHANNEL_KINDS, CHANNEL_TYPES_BY_KIND, CONSENT_MEDIUMS, CONSENT_PURPOSES, MEDIUMS_BY_CHANNEL_TYPE, allowedMediums, isMediumAllowed, validateChannelValue, normalizeChannelValue, readConsent, flattenConsent, summarizeBrandConsent, channelDisplayValue, humanizeEnum, channelKindForType, newId, draftFromChannel, emptyDraft, channelFromDraft, applyDraft, the status tone maps and the icon maps (CHANNEL_TYPE_ICONS, CHANNEL_KIND_ICONS, CONSENT_MEDIUM_ICONS, iconForType, iconForKind, iconForMedium).
Type Exports
ContactChannel, ContactBrand, ChannelKind, ChannelType, ChannelLabel, ConsentCache, ConsentCacheEntry, ConsentMediumMap, ConsentPurposeMap, ConsentRecord, ConsentAction, ConsentMedium, ConsentPurpose, ConsentStatus, ValidationCache, ValidationRecord, ValidationMethod, ValidationStatus, ChannelCreateInput, ChannelUpdateInput, ConsentEntryInput, ValidationEntryInput, ChannelDraft, FlatConsent, StatusTone, plus every component's *Props.
Differences from web
- Popovers and dialogs are bottom sheets. The brand × medium grid is a collapsible section per brand.
- The icon maps hold
DocyrusIconnames ('fal envelope') instead of lucide components. - Tooltips are inline text.
ContactActivityPanel
Typed activity timeline for a contact or record, with category tabs, a type filter, relationship summary, upcoming tasks, an emit-only Add menu and a detail sheet with replies and reactions.
ContactChannelsPanel
Docyrus-backed contact points and consent for a record — preferred channels, per-kind channel cards, brand consent and a consent / validation history, with every endpoint wired through useDocyrusContactChannels.