# rn-contact-channels-field URL: /docs/native/docyrus/contact-channels-field 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 ```bash pnpm dlx @docyrus/cli add @docyrus/rn-contact-channels-field ``` The 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. - **`ContactChannelsField`** is a compact summary: the primary channel per kind (with a `+N` badge), 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 calls `onDetailsClick` so you can open [rn-contact-channels-panel](/docs/native/docyrus/contact-channels-panel) instead. - **`ContactChannelsManager`** is 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**. - **`ConsentMatrix`** has 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 ```tsx import { ContactChannelsField, type ContactChannel } from '@/components/docyrus-native/contact-channels-field'; const [channels, setChannels] = useState([]); ``` ```tsx import { ContactChannelsManager } from '@/components/docyrus-native/contact-channels-field'; ``` ## 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` | Persist a new channel (temporary id) | | `onSave` | `(channel: ContactChannel) => void \| Promise` | Persist edited attributes | | `onMakePrimary` | `(channel: ContactChannel) => void \| Promise` | Promote to primary for its kind | | `onRemove` | `(channel: ContactChannel) => void \| Promise` | Remove / archive | | `onRecordConsent` | `(channel, brandKey, medium \| null, purpose, status) => void \| Promise` | 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 `DocyrusIcon` names (`'fal envelope'`) instead of lucide components. - Tooltips are inline text.