import { type VariantProps } from 'class-variance-authority'; import * as React from 'react'; import { type MessageChannel } from '../../molecules/channel-message'; /** * The channels this identity block is drawn for. * * **Figma has three members against `MessageChannel`'s five, and the mismatch runs both ways.** * `Avatar` (1033:14525) is keyed `Channel=RCS & SMS | WhatsApp | Email`: * * - **`RCS & SMS` is one variant covering two channels**, which in Strix's vocabulary is `rcs` plus * `messaging` — `messaging` is the member `ChannelMessage` already uses for SMS, and its manifest * records that `whatsapp` and `web-chat` reuse `messaging`'s bubble. So one Figma treatment maps to * two members, and no variant is missing. * - **`web-chat` has no Figma member at all** and takes the same treatment, because it is the same list * chrome. That is a fourth member on a three-member set, recorded as an extension rather than as * evidence. * - **`email` is excluded.** Its `Channel=Email` variant is *not an avatar*: its only layers are a * `gradient` frame and a 14/20 `Subject line`, with no icon tile, no company pill and no verified * badge — none of the three things this component composes. It is an email header, and Figma's * `Email` (1152:33529) is a 1332px thread with reply nesting, which is `EmailThread`'s. Building a * subject line here to satisfy an enum would put Track C's component inside Track B's directory. * * Derived with `Exclude` rather than written out, so a channel added to `MessageChannel` is a type * error here instead of a silently unhandled variant. */ export type ChannelAvatarChannel = Exclude; export declare const CHANNEL_AVATAR_CHANNELS: readonly ["messaging", "rcs", "whatsapp", "web-chat"]; /** * The sender identity above a channel conversation, from Figma `Avatar` (1033:14525) in the Multi * product demo file (`85sD2HI8wksP9Y7uDmJ9DK`), which GTM-870 names as the design authority. * * ### Two layouts, decided by the reported heights rather than by the layer list * * - **`RCS & SMS` is a column.** 32 (`Icon`) + 4 (`gap`) + 18 (`Company`) = **54**, inside a 64px frame. * The 10px remainder is not accounted for by any layer in the payload and is **not invented as * padding**: the root declares `paddingVertical` on the WhatsApp variant *only*, 10px has no spacing * token, and `no-arbitrary-tailwind` rightly rejects `py-[5px]`. Recorded as unexplained rather than * papered over. * - **`WhatsApp` is a row.** `paddingVertical: 8` + 24 (`Icon`) + 8 = **40**, exactly the reported * height, which is what rules out reading it as a column too. Its `gap` is 8, and its verified check * is a top-level sibling (`Verified Icon`, 12px) rather than nested in the tile — where the RCS * variant nests its badge inside `Icon` as `Icon / verified_user` at 11px. Both placements are kept, * because where the badge sits is the visible difference between the two channels' identity blocks. * * The badge snaps to **12px in both**: 11px has no spacing token and its nearest neighbour is the 12 * the other variant already uses, so snapping up costs one pixel and keeps one badge size rather than * inventing a second step. * * ### Base UI's `avatar` was checked and declined, by rendering it * * `@base-ui/react/avatar` exists and is unused by Strix, so it was probed rather than assumed — the * `otp-field` lesson. Rendered, `Avatar.Root` emits a bare `` **with no role**, `Avatar.Fallback` * emits another, and `Avatar.Image` renders **nothing at all in jsdom** because it is gated on the * image's load event, which jsdom never fires. Its entire value is remote-image load state plus an * initials fallback, and this atom has neither: the tile holds a local brand glyph, and there is no * image URL or initials anywhere in the payload. An `aria-label` on an unroled `` also names * nothing, so it would supply the *wrong* accessibility rather than none. Unlike `otp-field` — which * solved real problems (roving tabindex, paste, autofill) that hand-rolling got wrong — there is no * behaviour here to make headless. It remains the right primitive the day a photo avatar with a * fallback is wanted. * * ### Colour: three substitutions, each with its own reason * * - **The tile IS FIGMA'S NOW.** `Icon` is `color/gray/gray-850` #191f36. It was * `--color-surface-neutral-semi-medium`, refused as "a near-black chip drawn for a #000d25 page, which * in light mode is a dark hole", and chosen because it is "the same fill `ChannelMessage` uses for its * business bubble, so the identity block and the messages below it agree". Two things retired that: * rule 3 removed the light mode, and `ChannelMessage` moved to `--color-surface-field` (`gray-800`) * when its bubbles were reverted — so the agreement being preserved had already lapsed. Composing this * atom into the four channel screens is what surfaced it, as a stray #232e47 in the decoded pixels. * 1.19:1 against the page is not a conflict: a tile is not a control boundary, and the glyph on it * measures **16.27:1**, which is what 1.4.11 asks about. * - **The product glyph.** Figma fills it `color/brand/accesible-red-dark-theme` #ef223a, and that is * refused on **meaning**, not on contrast: it is Twilio's own logo colour, and hardcoding a red fill * would repaint every other company's logo that a caller passes in. `logo` is a slot, exactly as * `ChannelMessage`'s `media` is for the `Card image` asset set, and the wrapper's * `--color-icon-primary` is a default a caller's own fill overrides — **17.75:1 light / 13.52:1 dark** * against the tile, well clear of WCAG 1.4.11's 3:1 for a graphical object. * - **The verified badge.** Figma's WhatsApp check is `color/blue/blue-700` #0e3e92, which measures * **1.80:1** against Strix's dark `--color-surface-base` #0f172a; its RCS badge is unbound literals * (#4287f5 on #d9d9d9 with a #ffffff ring), and an unbound literal is a new palette entry rather * than a re-point. `--color-icon-accent` is #1866ee light — *exactly* `color/blue/blue-500`, the blue * the file itself declares — and measures **4.61:1 / 7.40:1** on the tile and **5.03:1 / 9.78:1** on * the page. Both are measured, because the badge overlaps the tile's edge in the RCS treatment and * sits wholly on the page in the WhatsApp one. * * **THE COMPANY PILL LOOKED LIKE A CLEAN PAIRING AND WAS NOT.** This read: it "keeps Figma's own * pairing, which happens to survive: `color/brand/ink` #000d25 is *exactly* light's * `--color-surface-inverse`". True — of the LIGHT value. `dark.css` re-points that role to * `--palette-secondary-800` #232e47, and dark is the only theme, so what rendered was never Figma's ink * at all. That is the same error `Phone-container`'s border made: reading a semantic role's value out of * the wrong layer. The pill is `--color-surface-avatar-pill` now, pinned to `brand/ink`, and the white * name on it measures **19.37:1** rather than the substitute's 13.52:1 — better, which is the usual tell. * * Found by composing this atom into the four channel screens and decoding the pixels: #232e47 kept * appearing in renders whose every stated colour had been reverted. * * **No border is drawn on the tile or the pill.** Figma draws none, and silence is not disagreement. * The tile measures 1.09:1 against a white page and the pill 1.32:1 against the dark one, so both are * weak edges — but WCAG 1.4.11 asks 3:1 of the boundary of a *control* or of a graphical object * conveying information, and neither is either: the glyph inside the tile clears 3:1 on its own, and * the pill's meaning is its text. Same call `ChannelMessage` made about its bubble, with the same * numbers. * * ### Accessibility * * The badge glyph is `aria-hidden` and paired with a real, visually hidden string, so "verified" is * read rather than merely drawn. An unlabelled `` with no text alternative was one of the eight * pattern ports' inherited bugs (`docs/AGENTS.md` §7) and is not repeated. Nothing here transitions, * so there is no `motion-reduce:transition-none` to add — asserted by a test rather than claimed. * * Every class below is a literal. Tailwind extracts class names by scanning source text, so a size or * a fill assembled from a variant value would generate no CSS while the rendered `className` looked * correct. */ declare const channelAvatarStyles: (props?: ({ channel?: "messaging" | "rcs" | "whatsapp" | "web-chat" | null | undefined; } & import("class-variance-authority/types").ClassProp) | undefined) => string; /** * The logo tile — Figma's `Icon`: 32px on `RCS & SMS`, 24px on `WhatsApp`, `radius: 50` on both. * * `relative` unconditionally so the RCS badge has a positioning context; harmless on WhatsApp, where * the badge is in the flow. No `overflow-hidden`: the badge is meant to break the circle's edge. */ declare const channelAvatarTileStyles: (props?: ({ channel?: "messaging" | "rcs" | "whatsapp" | "web-chat" | null | undefined; } & import("class-variance-authority/types").ClassProp) | undefined) => string; /** * The product glyph inside the tile — 16px in both treatments. * * Its own style function so the glyph's ink is derived and measured against the tile separately from * the tile's own fill, and so `colorTokensIn` finds exactly one ink here. */ declare const channelAvatarGlyphStyles: (props?: import("class-variance-authority/types").ClassProp | undefined) => string; /** * The verified badge — 12px, `--color-icon-accent`. * * Positioned on the tile's corner in the `RCS & SMS` treatment, where Figma nests it inside `Icon`, and * left in the flow in the `WhatsApp` one, where Figma has it as a row sibling. */ declare const channelAvatarBadgeStyles: (props?: ({ channel?: "messaging" | "rcs" | "whatsapp" | "web-chat" | null | undefined; } & import("class-variance-authority/types").ClassProp) | undefined) => string; /** * The company pill — Figma's `Company`: 18px tall, `radius: 50`, `paddingHorizontal: 8`, `gap: 4`. * * No vertical padding: the 18px *is* the 12/18 line box, and padding here would make it 26 and break * the column's 54px sum. */ declare const channelAvatarPillStyles: (props?: import("class-variance-authority/types").ClassProp | undefined) => string; /** * The company name — 12/18 Semibold. * * Figma reports `fontWeight: 375`, and `font/weight/Semibold` is the only weight variable this atom * declares: the mapping is by variable **name**, Semibold → 600. 375 is Whitney SSm's embedded value * and provenance only. The pair is `--text-size-figma-12` / `--leading-height-figma-12` rather than the * `body-xs` role, whose leading is 16px where the `Company` layer reports 18. */ declare const channelAvatarNameStyles: (props?: import("class-variance-authority/types").ClassProp | undefined) => string; export { channelAvatarStyles, channelAvatarTileStyles, channelAvatarGlyphStyles, channelAvatarBadgeStyles, channelAvatarPillStyles, channelAvatarNameStyles, }; type ChannelAvatarVariants = VariantProps; export interface ChannelAvatarProps extends React.ComponentPropsWithoutRef<'div'> { /** The company name, in the pill. Figma's `Company / Owl support`. */ name: React.ReactNode; /** * The channel. `messaging`, `rcs` and `web-chat` all take Figma's single `RCS & SMS` treatment; * `whatsapp` is its own row layout. `email` is deliberately not a member — see * {@link ChannelAvatarChannel}. * @default 'messaging' */ channel?: NonNullable; /** * The company's logo, 16px. A slot rather than a component, for the same reason `ChannelMessage`'s * `media` is: Figma's is the Twilio product mark, and a design system cannot supply anyone else's. * Pass it `aria-hidden` — the name beside it is the accessible text. */ logo?: React.ReactNode; /** * Whether to draw the verified badge. * @default true */ verified?: boolean; /** * The badge glyph. Defaults to `ShieldCheckIcon`, which is the nearest thing to Figma's * `verified_user`. */ badge?: React.ReactNode; /** * The badge's text alternative. Visually hidden and always rendered alongside the glyph, because a * decorative check conveys "verified" to sighted users only. * @default 'Verified business' */ verifiedLabel?: string; } /** * @example } /> * @example */ export declare const ChannelAvatar: React.ForwardRefExoticComponent>; //# sourceMappingURL=channel-avatar.d.ts.map