Contact Channels Field
A backend-agnostic, fully controlled editor for a record's contact channels, their consent cache and validation status.
ContactChannelsField is a compact, read-at-a-glance summary of a record's contact channels —
the primary email / phone / messaging / social entry per kind (with a +N badge for the rest) plus a
per-brand consent grid. A Details link opens a dialog with the full
ContactChannelsManager for adding, editing and removing channels and
editing consent & validation. It mirrors the snake_case shape of the Contact Channels & Consent API,
so the same ContactChannel[] round-trips with the Docyrus-backed
Contact Channels Panel — but the field itself needs
no backend: give it value + onChange and it works in any form.
Installation
pnpm dlx @docyrus/cli add @docyrus/ui-contact-channels-fieldUsage
import { useState } from 'react';
import {
ContactChannelsField,
type ContactChannel
} from '@docyrus/ui/components/contact-channels-field';
function Example() {
const [channels, setChannels] = useState<ContactChannel[]>([]);
return <ContactChannelsField value={channels} onChange={setChannels} />;
}Opening the Docyrus panel from Details
Pass onDetailsClick to take over the Details link — typically to open the backend-backed
ContactChannelsPanel as a dialog instead of the
built-in editor:
const [open, setOpen] = useState(false);
<ContactChannelsField value={channels} brands={brands} readOnly onDetailsClick={() => setOpen(true)} />
<ContactChannelsPanel open={open} onOpenChange={setOpen} client={client} appSlug="base" dataSourceSlug="contact" recordId={recordId} />Comprehensive editor
ContactChannelsManager is the full backend-agnostic editor rendered inside the Details dialog —
use it directly (inline) when you don't want the summary chrome:
import { ContactChannelsManager } from '@docyrus/ui/components/contact-channels-field';
<ContactChannelsManager value={channels} onChange={setChannels} brands={brands} />Inside a TanStack form
Use the ContactChannelsFormField wrapper to bind the channel array to a form field. It edits inline
with ContactChannelsManager and surfaces validation errors.
import { ContactChannelsFormField } from '@docyrus/ui/components/form-fields';
<ContactChannelsFormField
field={{ id: 'channels', slug: 'channels', name: 'Contact channels', type: 'field-json' }}
form={form}
brands={brands}
/>Channel & consent model
Each channel carries a channel_kind (email · phone · messaging · social · web) and a
channel_type (email, mobile, whatsapp, linkedin, …). At most one channel per kind is
is_primary. The consent cache is a three-level map keyed brand → medium → purpose; the field's
inline consent editor only offers mediums that are valid for the channel type (e.g. a mobile
channel exposes call and sms, an email channel exposes email), matching the medium ↔
channel-type rules the API enforces server-side. A new channel defaults to
transactional consent opted_in at the organization-wide / medium-agnostic level.
API Reference
ContactChannelsField (summary)
| Prop | Type | Default | Description |
|---|---|---|---|
value | ContactChannel[] | — | Controlled list of channels. |
onChange | (next: ContactChannel[]) => void | — | Emitted when channels are edited in the Details dialog. |
brands | ContactBrand[] | — | Brands for the consent summary (one row each); omit for a single organization-wide row. |
title | string | 'Contact Points' | Summary heading. |
hideDetails | boolean | false | Hide the Details link. |
detailsLabel | string | 'Details' | Label for the Details link. |
onDetailsClick | () => void | — | When set, Details calls this instead of opening the built-in dialog. |
showConsent | boolean | true | Show the consent summary section. |
disabled | boolean | false | Disable editing in the built-in dialog. |
readOnly | boolean | false | Render the built-in dialog read-only. |
emptyText | string | 'No contact points yet.' | Placeholder when there are no channels. |
className | string | — | Additional class on the root. |
ContactChannelsManager (full editor)
| Prop | Type | Default | Description |
|---|---|---|---|
value | ContactChannel[] | — | Controlled list of channels. |
onChange | (next: ContactChannel[]) => void | — | Emitted on any add / edit / remove / make-primary. |
brands | ContactBrand[] | — | Brands for labelling / scoping consent. |
disabled | boolean | false | Disable every interaction. |
readOnly | boolean | false | Render read-only (hides editors and action menus). |
enableConsent | boolean | true | Show the inline consent editor per channel. |
enableValidation | boolean | true | Show validation status chips. |
groupByKind | boolean | true | Group channels under kind headers. |
emptyText | string | 'No contact channels yet.' | Placeholder when there are no channels. |
ContactChannelsFormField
Extends the standard Docyrus form-field props (field, form, disabled, required, className)
with brands, enableConsent, enableValidation and groupByKind. Edits inline with
ContactChannelsManager.
Exports
| Export | Description |
|---|---|
ContactChannelsField | The compact summary + Details dialog. |
ContactChannelsManager | The full backend-agnostic editor (rendered in the Details dialog). |
ChannelEditor | Backend-agnostic single-channel attribute editor. |
ConsentMatrix | Consent-cache editor (medium × purpose) for one channel. |
ConsentStatusBadge / ValidationStatusBadge / VerifiedBadge | Status chips. |
normalizeChannelValue / validateChannelValue | Value helpers (email lower-casing, format checks). |
flattenConsent / readConsent / summarizeBrandConsent | Consent-cache readers. |
CHANNEL_TYPES_BY_KIND / MEDIUMS_BY_CHANNEL_TYPE / allowedMediums | Enum + compatibility maps. |
Type Exports
| Type | Description |
|---|---|
ContactChannel | A channel row (snake_case, mirrors the API response). |
ContactBrand | Brand lookup (id, name, logo_url, color_primary). |
ConsentCache | brand → medium → purpose consent map. |
ConsentRecord / ValidationRecord | Ledger rows (used by the panel). |
ChannelKind / ChannelType / ConsentPurpose / ConsentMedium / ConsentStatus / ValidationStatus | Enumerations. |