import { type VariantProps } from 'class-variance-authority'; import * as React from 'react'; /** * The channel a message is being carried over. * * **Five members, where Figma's `Message` (1027:11104) varies across only three.** Its grid is * `Type=Business|User` x `Channel=Messaging|Email|RCS`, so `whatsapp` and `web-chat` have no member * of their own — they reuse `messaging`'s bubble exactly, and differ only in the chrome around the * message list, which is `PhoneFrame`'s business. The extra members exist so that a composition can * name one channel once and pass it to both components, rather than mapping WhatsApp to "messaging" * at every call site. **Nothing is missing from the Figma set; do not go looking for it.** * * Named `MessageChannel` rather than `ChannelMessageChannel` because `PhoneFrame`, `ChannelAvatar` * and `EmailThread` all import it. Note it shadows the DOM's own `MessageChannel` interface inside * any module that imports it, which is intentional — a design system has no use for the postMessage * channel type and the plan fixes this name. */ export type MessageChannel = 'messaging' | 'rcs' | 'whatsapp' | 'web-chat' | 'email'; /** Figma's `Type` axis, named for who is speaking rather than for a type. */ export type MessageAuthor = 'business' | 'user'; export declare const CHANNEL_MESSAGE_CHANNELS: readonly ["messaging", "rcs", "whatsapp", "web-chat", "email"]; export declare const CHANNEL_MESSAGE_AUTHORS: readonly ["business", "user"]; /** * One message in a channel conversation, from Figma `Message` (1027:11104) in the Multi product demo * file (`85sD2HI8wksP9Y7uDmJ9DK`), which GTM-870 names as the design authority for this work. * * The root is alignment and nothing else. It paints no colour and sets no padding, so a bubble fill * can never be shadowed by something the root emitted — the contrast test asserts exactly that. * * **Alignment is not decoration here.** The two bubble fills measure 4.61:1 apart in light and only * **1.94:1 apart in dark**, so hue alone would not distinguish an incoming message from an outgoing * one and WCAG 1.4.1 would fail. Side is the non-chromatic signal, `data-author` the programmatic * one. Figma agrees about the layout: it draws the two `Channel=Messaging` variants at the same 52px * with the same padding, so side is the only thing between them there too. */ declare const channelMessageStyles: (props?: ({ author?: "user" | "business" | null | undefined; } & import("class-variance-authority/types").ClassProp) | undefined) => string; /** * The bubble: fill, radius, padding and the gap between stacked lines. * * Geometry is Figma's, checked against the variant heights rather than against the set frame: * * - **Messaging** — `paddingHorizontal: 8`, `paddingVertical: 8`, `gap: 8`, and 36px of text (two * 18px lines) makes 8 + 36 + 8 = **52**, exactly the height reported for both variants. * - **Email** — `fill: none`, `gap: 16`, no padding and no radius. An email message is distinguished * by ink rather than by a bubble, and the 180px/214px variant heights are content, not a box. * - **RCS** — `radius: 10` (`--radius-large`), and `gap-0`: Figma reports `gap: -189`, which is not a * gap. Its four layers are 77 (`Card image`) + 88 (body) + 34 + 34 (two link rows) = **233**, * exactly the variant height, so they are flush and the negative number is an absolute-position * artefact. `gap-0` and not `gap-none`: `--spacing-none` was dropped from the token layer * precisely because it carried no design intent, so `.gap-none` compiles to nothing — * `gap-0` is Tailwind's own zero, not a token. * * **DEV MODE SETTLES THE RADIUS, and one corner is square.** This said "the messaging root's corner * radius is not variable-bound in the payload, so it is neither 0 nor 10 on the evidence … a * square-cornered bubble beside a 10px one would be inventing a second step". It is not a second step: * every bubble is 10px on THREE corners and square on the fourth, and which one depends on the * speaker — `rounded-br-none` for `business`, `rounded-bl-none` for `user`. That is the tail, the * device every chat UI uses to point a bubble at its sender, and carrying `rounded-large` on all four * corners removed it. REST reports a radius binding nested under `rectangleCornerRadii` and an unbound * literal is absent, which is why the payload looked silent; the generated code is explicit. * * **THE FILLS ARE FIGMA'S NOW, and rule 3 is what changed.** Figma paints the business bubble * `color/gray/gray-800` #232b45 with white text (**13.97:1**) and the user bubble `color/gray/gray-50` * #f3f4f7 with `color/brand/ink` text (**17.61:1**) — a dark chip beside a near-white one, which is * the whole device for telling the two speakers apart. Both were refused because "ported literally, * #f3f4f7 measures 1.10:1 against a white page and vanishes, and #232b45 needs a light ink that * `--color-text-primary` does not supply in light mode". There is no white page. * * The substitute for the user bubble was `--color-surface-accent-extra-strong` #1152c2 — a **blue**, * which is not a lighter version of Figma's near-white chip but a different idea. `gray-800` is * `--color-surface-field`, already minted for the field atoms that share the literal. * * - business: `--color-surface-neutral-semi-medium`, opaque in both themes (#f1f5ff / #232e47), * carrying `--color-text-primary` at **17.75:1 light / 13.52:1 dark**. * - user: `--color-surface-accent-extra-strong`, opaque in both (#1866ee / #1152c2), carrying * `--color-text-inverse` at **5.03:1 / 6.98:1**. #1866ee is *exactly* `color/blue/blue-500`, which * the atom declares as a variable, so the outgoing bubble is the one place that value belongs. * * `--color-surface-primary` was measured and rejected: it is `rgba(255, 255, 255, 0.08)` in dark, and * a translucent fill's composite depends on its backdrop, so it cannot carry a fixed ink across the * themes. A test asserts both chosen fills are an opaque hex in both themes so that substitution * cannot be made silently. * * **No border is drawn.** On the page Figma draws, the business bubble's fill measures **1.39:1** * against it and the user bubble's **17.61:1**, so the business edge is a weak one. WCAG * 1.4.11 asks 3:1 of the boundary of a *control* or of a graphical object conveying information; a * message bubble is neither, and its edge carries nothing the text and the alignment do not. This is * the same call `ToolCall` made about its pill. The RCS card's internal * hairline is different and *is* held to 3:1 — see `channelMessageDividerStyles`. * * **RCS is author-invariant.** Figma has no `Type=User, Channel=RCS` member, so the card keeps the * business treatment whichever author is passed; only the alignment follows the author. Silence is * not disagreement (docs/AGENTS.md §8), so a user-authored RCS card is not invented, merely not * repainted. */ declare const channelMessageBubbleStyles: (props?: ({ channel?: "email" | "messaging" | "rcs" | "whatsapp" | "web-chat" | null | undefined; author?: "user" | "business" | null | undefined; } & import("class-variance-authority/types").ClassProp) | undefined) => string; /** * The message ink and its type role. * * Every text layer in the atom is **12/18 at `font/weight/Book`**, mapped by variable *name* to 400. * Figma reports 325, which is Whitney SSm's embedded value and provenance only — `font-weight: 325` * is not Book to a browser. The pair is `--text-size-figma-12` / `--leading-height-figma-12` rather * than the `body-xs` role, whose leading is 16px: 16 + 8 + 8 is 32 and Figma's messaging variant is * 52, which only 18px leading produces. `Badge`'s `small` size and `ToolCall`'s pill choose the same * pair for the same reason. The utilities are written out literally rather than reached through * `` because the ink varies per combination and `Typography` would add a second * box to say one thing. * * **THE USER BUBBLE'S INK MOVED WITH ITS FILL.** It was `--color-text-inverse`, which is white — and * white on Figma's near-white chip is **1.10:1**. That is the second half of a fill substitution: the * blue bubble needed a light ink, so one was chosen, and reverting only the fill would have left an * invisible message. `--color-text-on-bubble-user` is Figma's `color/brand/ink` at **17.61:1**. * * **Email's user ink is the `InfoRow` substitution again.** Figma paints it `color/gray/gray-300` * #9aa0b4, which measures **2.61:1** on a light page — a WCAG 1.4.3 failure the dark-only source * never had to face. `--color-text-tertiary` is 4.78:1 light / 8.17:1 dark. That the two email * variants differ in ink weight rather than in a bubble is the design's own distinction, kept. */ declare const channelMessageTextStyles: (props?: ({ channel?: "email" | "messaging" | "rcs" | "whatsapp" | "web-chat" | null | undefined; author?: "user" | "business" | null | undefined; } & import("class-variance-authority/types").ClassProp) | undefined) => string; /** * The RCS `Card image` frame. A slot, not a component: Figma's `Card image` (1536:22843) is an * 18-variant asset set keyed by subject, which is an asset library rather than something to build — * the same call `ScenarioCard` made. Its 77px height belongs to the asset, so none is set here. */ declare const channelMessageMediaStyles: (props?: import("class-variance-authority/types").ClassProp | undefined) => string; /** Figma `Frame 1010105595`: `paddingHorizontal: 12`, `paddingVertical: 8`, `gap: 8`, 88px tall. */ declare const channelMessageBodyStyles: (props?: import("class-variance-authority/types").ClassProp | undefined) => string; /** * One RCS action row — Figma `Frame 1010105590` / `Frame 1010105591`: 34px tall, * `paddingHorizontal: 12`, `paddingVertical: 8`, `gap: 8`, and a centre-aligned 12/18 label. 8 + 18 + * 8 = 34 exactly. * * The row is a frame, not a control: the caller supplies the control. Figma's label is `Link` and a * link in this position needs an href, a handler or a form, none of which a presentational bubble can * invent — so `actions` takes rendered nodes and this frame only positions them. */ declare const channelMessageActionStyles: (props?: import("class-variance-authority/types").ClassProp | undefined) => string; /** * The hairline between two RCS action rows — Figma `Line 602`. * * Its own style function so the boundary is derived and measured separately from the row's padding, * and so `colorTokensIn`'s `borders` bucket contains exactly one token. * * Figma draws it `color/gray/gray-700` #38425e at `borderWidth: 0.5`. Two departures. The width snaps * to 1px (`border-t`, `--spacing-borders-thin`): there is no 0.5px token, and a sub-pixel rule * renders inconsistently across device pixel ratios anyway. The colour is not ported — * `--color-border-neutral-medium`, the nearest token to #38425e, measures **1.42:1 light / 2.83:1 * dark** against this card's own fill and would be a rule visible in one theme only. * `--color-border-control` is **4.38:1 light / 4.20:1 dark** against the card, clearing WCAG 1.4.11's * 3:1. That threshold is applied here, and not to the bubble's own edge, because this rule is the only * thing separating two adjacent controls. * * Figma has exactly one `Line 602`, between the two link rows — the four layer heights already sum to * the variant height with nothing left over for a rule above the first row — so it is drawn on every * action except the first. */ declare const channelMessageDividerStyles: (props?: import("class-variance-authority/types").ClassProp | undefined) => string; /** * The container wrapping the bubble and optional meta line (timestamp/device). * Aligns both children consistently with the author side (items-start for business, items-end for user). */ declare const channelMessageContainerStyles: (props?: ({ author?: "user" | "business" | null | undefined; } & import("class-variance-authority/types").ClassProp) | undefined) => string; /** * The meta line under the bubble: timestamp and/or sending device. * 12/18 in secondary text, matching the bubble text ramp. */ declare const channelMessageMetaStyles: (props?: import("class-variance-authority/types").ClassProp | undefined) => string; export { channelMessageStyles, channelMessageContainerStyles, channelMessageBubbleStyles, channelMessageTextStyles, channelMessageMediaStyles, channelMessageBodyStyles, channelMessageActionStyles, channelMessageDividerStyles, channelMessageMetaStyles, }; type ChannelMessageVariants = VariantProps; export interface ChannelMessageProps extends React.ComponentPropsWithoutRef<'div'> { /** * The channel. `whatsapp` and `web-chat` render `messaging`'s bubble exactly; they exist so a * composition can name one channel for both this and `PhoneFrame`. * @default 'messaging' */ channel?: NonNullable; /** * Who is speaking. Drives the alignment as well as the fill, because the two fills are only 1.94:1 * apart in dark and hue alone would fail WCAG 1.4.1. * @default 'business' */ author?: NonNullable; /** * The RCS card's image, from Figma's `Card image` asset set. **Rendered only when * `channel="rcs"`** — Figma draws it nowhere else, and rendering it elsewhere would invent a * variant. */ media?: React.ReactNode; /** * The RCS card's action rows, one node per row, separated by Figma's single hairline. Each node is * the caller's own control — a `]}> * Your suite is ready early. * */ export declare const ChannelMessage: React.ForwardRefExoticComponent>; //# sourceMappingURL=channel-message.d.ts.map