# Contact Channels Field URL: /docs/web/components/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`](#comprehensive-editor) 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](/docs/web/docyrus/contact-channels-panel) — but the field itself needs **no backend**: give it `value` + `onChange` and it works in any form. **Demo:** ```tsx 'use client'; import { useState } from 'react'; import { ContactChannelsField, type ContactBrand, type ContactChannel } from '@docyrus/ui/components/contact-channels-field'; const BRANDS: ContactBrand[] = [{ id: '0190aaaa', name: 'Acme', color_primary: 'indigo-500' }, { id: '0190bbbb', name: 'Globex', color_primary: 'emerald-500' }]; const SEED: ContactChannel[] = [ { id: '1', channel_kind: 'email', channel_type: 'email', value: 'ada@example.com', value_raw: 'ada@example.com', label: 'work', is_primary: true, is_verified: true, validation_status: 'valid', validated_on: '2026-06-01T08:00:00Z', consent: { _default: { _default: { transactional: { status: 'opted_in', source: 'default' } } }, '0190aaaa': { email: { marketing: { status: 'opted_in', on: '2026-06-10T09:00:00Z', source: 'web_form' } } } } }, { id: '2', channel_kind: 'phone', channel_type: 'mobile', value: '+905551112233', value_raw: '+90 555 111 22 33', country: 'TR', label: 'personal', is_primary: true }, { id: '3', channel_kind: 'social', channel_type: 'linkedin', value: 'in/ada-lovelace', value_raw: 'in/ada-lovelace' } ]; export function ContactChannelsFieldDemo() { const [channels, setChannels] = useState(SEED); return (
); } ``` ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/ui-contact-channels-field ``` **Dependencies:** - [lucide-react](https://www.npmjs.com/package/lucide-react) ## Usage ```tsx import { useState } from 'react'; import { ContactChannelsField, type ContactChannel } from '@docyrus/ui/components/contact-channels-field'; function Example() { const [channels, setChannels] = useState([]); return ; } ``` ### Opening the Docyrus panel from Details Pass `onDetailsClick` to take over the Details link — typically to open the backend-backed [`ContactChannelsPanel`](/docs/web/docyrus/contact-channels-panel) as a dialog instead of the built-in editor: ```tsx const [open, setOpen] = useState(false); setOpen(true)} /> ``` ### 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: ```tsx import { ContactChannelsManager } from '@docyrus/ui/components/contact-channels-field'; ``` ### 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. ```tsx import { ContactChannelsFormField } from '@docyrus/ui/components/form-fields'; ``` ## 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. |